papermill-agent 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -1
- data/README.md +29 -5
- data/lib/papermill-agent.rb +1 -1
- data/lib/papermill-agent/agent.rb +15 -8
- data/lib/papermill-agent/configurator.rb +26 -2
- data/lib/papermill-agent/logger.rb +1 -1
- data/lib/papermill-agent/rack/collector.rb +1 -0
- data/lib/papermill-agent/railtie.rb +14 -0
- data/lib/papermill-agent/response_adapters/base.rb +7 -3
- data/lib/papermill-agent/tasks.rb +21 -0
- data/spec/agent_spec.rb +28 -3
- data/spec/configurator_spec.rb +65 -8
- data/spec/response_parser_spec.rb +3 -5
- data/spec/spec_helper.rb +4 -0
- metadata +18 -3
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,16 +3,40 @@ WARNING: This is pre-alpha software. Use at your own risk until 1.0.
|
|
3
3
|
|
4
4
|
The papermill agent parses responses from your web application.
|
5
5
|
|
6
|
-
|
6
|
+
# Installation #
|
7
7
|
|
8
|
-
|
8
|
+
## Step 1: Create config/papermill.yml ##
|
9
9
|
|
10
|
-
|
10
|
+
Papermill's configuration information is stored in a yml file located at
|
11
|
+
`[PROJECT_ROOT]/config/papermill.yml`. The only required configuration
|
12
|
+
option is your api token:
|
13
|
+
|
14
|
+
# config/papermill.yml
|
15
|
+
token: you-api-token
|
16
|
+
|
17
|
+
## Step 2: include the middleware layer for request capturing and logging ##
|
18
|
+
|
19
|
+
Papermill works by capturing every request submitted to your application and
|
20
|
+
periodically sending them off to the PapermillApp servers. To enable this,
|
21
|
+
you must add the Papermill middleware class to your application's middleware
|
22
|
+
stack.
|
23
|
+
|
24
|
+
In rails, this is done in either `config/environment.rb` or `config/application.rb`,
|
25
|
+
depending on whether you're using rails 2 or rails 3. In a sinatra or rack app,
|
26
|
+
you simply add the middleware via rack's `use` method.
|
27
|
+
|
28
|
+
See the examples below.
|
29
|
+
|
30
|
+
### rails 2.x ###
|
11
31
|
# in config/environment.rb
|
12
32
|
config.middleware.use 'Papermill::Collector'
|
13
33
|
|
14
|
-
|
15
|
-
# in config.
|
34
|
+
### rails 3 ###
|
35
|
+
# in config/application.rb
|
36
|
+
config.middleware.use 'Papermill::Collector'
|
37
|
+
|
38
|
+
### sinatra or a rack application ###
|
39
|
+
# in config.ru or in a Rack::Builder stack
|
16
40
|
use Papermill::Collector
|
17
41
|
|
18
42
|
|
data/lib/papermill-agent.rb
CHANGED
@@ -3,7 +3,7 @@ $:.unshift File.dirname(File.expand_path(__FILE__))
|
|
3
3
|
|
4
4
|
module Papermill
|
5
5
|
# papermill endpoint which will receive client requests
|
6
|
-
API_ENDPOINT = 'http://
|
6
|
+
API_ENDPOINT = 'http://papermillapp.com/api'
|
7
7
|
|
8
8
|
autoload :Agent, 'papermill-agent/agent'
|
9
9
|
autoload :Collector, 'papermill-agent/rack/collector'
|
@@ -12,6 +12,7 @@ module Papermill
|
|
12
12
|
UPDATE_INTERVAL = 10
|
13
13
|
|
14
14
|
attr_reader :last_sent, :mutex, :config, :logger
|
15
|
+
attr_accessor :disabled
|
15
16
|
|
16
17
|
# request timeout threshold for sending data to papermill
|
17
18
|
@@request_timeout = 5
|
@@ -19,22 +20,28 @@ module Papermill
|
|
19
20
|
@@request_timeout
|
20
21
|
end
|
21
22
|
|
23
|
+
def disabled?
|
24
|
+
!!disabled
|
25
|
+
end
|
26
|
+
|
22
27
|
def start
|
23
28
|
@last_sent = Time.now
|
24
29
|
@mutex = Mutex.new
|
25
|
-
@config = Configurator.new
|
26
30
|
@logger = Logger.new
|
31
|
+
@config = Configurator.new
|
27
32
|
logger.info "\n\n=============== Starting papermill-agent at #{Time.now} in process #{Process.pid}"
|
28
33
|
Thread.abort_on_exception = true
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
unless disabled?
|
36
|
+
@worker_thread = Thread.new do
|
37
|
+
loop do
|
38
|
+
if time_since_last_sent > UPDATE_INTERVAL
|
39
|
+
send_data_to_papermill
|
40
|
+
@last_sent = Time.now
|
41
|
+
end
|
42
|
+
sleep_time = seconds_until_next_run
|
43
|
+
sleep sleep_time if sleep_time > 0
|
35
44
|
end
|
36
|
-
sleep_time = seconds_until_next_run
|
37
|
-
sleep sleep_time if sleep_time > 0
|
38
45
|
end
|
39
46
|
end
|
40
47
|
end
|
@@ -7,19 +7,33 @@ module Papermill
|
|
7
7
|
|
8
8
|
def initialize(config_source = 'config/papermill.yml')
|
9
9
|
load_config(config_source)
|
10
|
+
after_init
|
10
11
|
end
|
11
12
|
|
13
|
+
# Loads the papermill configuration.
|
14
|
+
#
|
15
|
+
# By default, the papermill configuration is read from a yaml file located
|
16
|
+
# at ./config/papermill.yml. Still, the configuration source can either be
|
17
|
+
# a yaml file of any name or a ruby hash.
|
18
|
+
#
|
19
|
+
# If the configuration file cannot be found, the agent will be disabled.
|
12
20
|
def load_config(config_source)
|
13
21
|
@config = case
|
14
22
|
when config_source.class == String
|
15
|
-
|
23
|
+
begin
|
24
|
+
YAML.load_file(config_source)
|
25
|
+
rescue Errno::ENOENT => e
|
26
|
+
disable_agent
|
27
|
+
Agent.instance.logger.info "The papermill configuration file does not exist. The papermill agent is disabled."
|
28
|
+
return nil
|
29
|
+
end
|
16
30
|
when config_source.class == Hash
|
17
31
|
config_source
|
18
32
|
end
|
19
33
|
end
|
20
34
|
|
21
35
|
def token
|
22
|
-
config['token']
|
36
|
+
(config && config.has_key?('token')) ? config['token'] : nil
|
23
37
|
end
|
24
38
|
|
25
39
|
def endpoint
|
@@ -50,6 +64,16 @@ module Papermill
|
|
50
64
|
end
|
51
65
|
|
52
66
|
private
|
67
|
+
# If there is no token, disable the agent.
|
68
|
+
def after_init
|
69
|
+
if token.nil?
|
70
|
+
disable_agent
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def disable_agent
|
75
|
+
Agent.instance.disabled = true
|
76
|
+
end
|
53
77
|
|
54
78
|
# holds the internal config hash
|
55
79
|
def config
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'papermill-agent'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Papermill
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
rake_tasks do
|
7
|
+
require 'papermill-agent/tasks'
|
8
|
+
end
|
9
|
+
|
10
|
+
# initializer "..." do |app|
|
11
|
+
# app.config.middleware.use "Papermill::Collector"
|
12
|
+
# end
|
13
|
+
end
|
14
|
+
end
|
@@ -30,11 +30,15 @@ module Papermill
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def extract_env_info
|
33
|
-
result_hash =
|
33
|
+
result_hash = {}
|
34
|
+
|
35
|
+
# Hash#select returns an array in 1.8, so we have to do some manual
|
36
|
+
# iteration to extract the key/value pairs from the environment hash.
|
37
|
+
ENV_CAPTURE_FIELDS.each { |key| result_hash[key] = env[key] }
|
34
38
|
|
35
39
|
# rename rack.* keys b/c mongo does not allow key names containing a .
|
36
|
-
|
37
|
-
|
40
|
+
naughty_rack_keys = ENV_CAPTURE_FIELDS.select { |key| key =~ /^rack\./ }
|
41
|
+
naughty_rack_keys.each do |key|
|
38
42
|
new_key = key.gsub(/^rack\./, '').upcase
|
39
43
|
result_hash[new_key] = result_hash.delete(key)
|
40
44
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'papermill-agent'
|
2
|
+
|
3
|
+
namespace :papermill do
|
4
|
+
desc "Generate a papermill configuration file"
|
5
|
+
task :create_config do
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
# don't overwrite existing configuration files
|
9
|
+
if File.exists?('./config/papermil.yml')
|
10
|
+
fail("A papermill configuration file already exists. Please remove it and try again.")
|
11
|
+
end
|
12
|
+
|
13
|
+
token = ENV['TOKEN'] || 'replace-with-your-token'
|
14
|
+
|
15
|
+
File.open('./config/papermill.yml', 'w') do |file|
|
16
|
+
config = {'token' => token}
|
17
|
+
YAML.dump(config, file)
|
18
|
+
end
|
19
|
+
STDOUT.puts "Your papermill configuration file has been created at config/papermill.yml."
|
20
|
+
end
|
21
|
+
end
|
data/spec/agent_spec.rb
CHANGED
@@ -147,7 +147,7 @@ module Papermill
|
|
147
147
|
|
148
148
|
describe 'the api endpoint' do
|
149
149
|
subject { Papermill::API_ENDPOINT }
|
150
|
-
it { should == 'http://
|
150
|
+
it { should == 'http://papermillapp.com/api' }
|
151
151
|
end
|
152
152
|
|
153
153
|
describe 'request timeout interval' do
|
@@ -188,7 +188,7 @@ module Papermill
|
|
188
188
|
|
189
189
|
describe 'serializing the payload' do
|
190
190
|
it 'jsonifies the payload data' do
|
191
|
-
agent.jsonify_payload.should ==
|
191
|
+
agent.jsonify_payload.should == Storage.store.flatten.to_json
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
@@ -201,7 +201,7 @@ module Papermill
|
|
201
201
|
end
|
202
202
|
|
203
203
|
it 'sends a request to the papermill api endpoint' do
|
204
|
-
RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload =>
|
204
|
+
RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload => Storage.store.flatten.to_json)
|
205
205
|
agent.send_data_to_papermill
|
206
206
|
end
|
207
207
|
|
@@ -234,5 +234,30 @@ module Papermill
|
|
234
234
|
end
|
235
235
|
end
|
236
236
|
|
237
|
+
describe 'disabling the papermill agent' do
|
238
|
+
context 'when disabled is set to true' do
|
239
|
+
before { agent.disabled = true }
|
240
|
+
after { agent.disabled = false }
|
241
|
+
|
242
|
+
it 'is disabled' do
|
243
|
+
agent.should be_disabled
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'wen disabled is set to false' do
|
248
|
+
before { agent.disabled = false }
|
249
|
+
|
250
|
+
it 'is disabled' do
|
251
|
+
agent.should_not be_disabled
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context 'by default' do
|
256
|
+
it 'is not disabled' do
|
257
|
+
agent.should_not be_disabled
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
237
262
|
end
|
238
263
|
|
data/spec/configurator_spec.rb
CHANGED
@@ -8,6 +8,35 @@ module Papermill
|
|
8
8
|
YAML.should_receive(:load_file).with('filename')
|
9
9
|
Configurator.new('filename')
|
10
10
|
end
|
11
|
+
|
12
|
+
context 'the config file does not exist' do
|
13
|
+
after { agent.disabled = false }
|
14
|
+
|
15
|
+
it 'disables the agent' do
|
16
|
+
lambda {
|
17
|
+
Configurator.new('does not exist')
|
18
|
+
}.should change(agent, :disabled?).to(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'does not raise a file load error' do
|
22
|
+
lambda {
|
23
|
+
Configurator.new('does not exist')
|
24
|
+
}.should_not raise_error(Errno::ENOENT)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sets config to nil' do
|
28
|
+
config = Configurator.new('does not exist')
|
29
|
+
config.send(:config).should be_nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when no token key is found' do
|
34
|
+
it 'disables the agent' do
|
35
|
+
lambda {
|
36
|
+
Configurator.new({})
|
37
|
+
}.should change(agent, :disabled?).to(true)
|
38
|
+
end
|
39
|
+
end
|
11
40
|
end
|
12
41
|
|
13
42
|
context 'when a hash is passed in' do
|
@@ -22,14 +51,19 @@ module Papermill
|
|
22
51
|
end
|
23
52
|
|
24
53
|
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
54
|
describe 'the token' do
|
31
|
-
|
32
|
-
|
55
|
+
subject { config.token }
|
56
|
+
|
57
|
+
context 'when the token exists' do
|
58
|
+
let(:config) { Configurator.new({'token' => 'token'}) }
|
59
|
+
|
60
|
+
it { should == 'token' }
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when the token does not exist' do
|
64
|
+
let(:config) { Configurator.new({}) }
|
65
|
+
|
66
|
+
it { should be_nil }
|
33
67
|
end
|
34
68
|
end
|
35
69
|
|
@@ -159,8 +193,9 @@ module Papermill
|
|
159
193
|
end
|
160
194
|
|
161
195
|
describe 'retrieving a setting for the current environment' do
|
196
|
+
let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
|
197
|
+
|
162
198
|
context 'for a given environment' do
|
163
|
-
let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
|
164
199
|
before { config.environment = 'production' }
|
165
200
|
|
166
201
|
context 'for a setting that has a value' do
|
@@ -172,9 +207,31 @@ module Papermill
|
|
172
207
|
context 'for a setting that does not have a value' do
|
173
208
|
it 'returns nil' do
|
174
209
|
config.setting('does not exist').should == nil
|
210
|
+
config.setting('does not exist').should === nil
|
211
|
+
config.setting('does not exist').should be_instance_of NilClass
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'does not return false' do
|
215
|
+
config.setting('does not exist').should_not === false
|
216
|
+
config.setting('does not exist').should_not == false
|
217
|
+
config.setting('does not exist').should_not === FalseClass
|
218
|
+
config.setting('does not exist').should_not be_instance_of FalseClass
|
175
219
|
end
|
176
220
|
end
|
177
221
|
end
|
178
222
|
|
223
|
+
context 'when trying to access a property with no environment' do
|
224
|
+
before { config.environment = 'not defined' }
|
225
|
+
subject { config.setting('does not exist') }
|
226
|
+
|
227
|
+
it 'should not return false' do
|
228
|
+
subject.should_not == false
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should return nil' do
|
232
|
+
subject.should == nil
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
179
236
|
end
|
180
237
|
end
|
@@ -14,14 +14,12 @@ module Papermill
|
|
14
14
|
end
|
15
15
|
|
16
16
|
context 'in a rails app' do
|
17
|
-
|
18
|
-
# self.class.module_eval { remove_const(:Sinatra) if defined?(Sinatra) }
|
17
|
+
class Rails
|
19
18
|
end
|
20
19
|
|
21
20
|
it 'uses the rails adapter' do
|
22
|
-
|
23
|
-
|
24
|
-
ResponseParser.parse_klass.should == ResponseAdapters::Rails
|
21
|
+
ResponseAdapters::Rails.should_receive(:new).and_return(mock('object', :parse => nil))
|
22
|
+
ResponseParser.parse(200, {}, [], {})
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.2
|
9
|
+
version: 0.1.0
|
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: 2011-01-
|
17
|
+
date: 2011-01-07 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +32,19 @@ dependencies:
|
|
32
32
|
version: 1.6.1
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: json_pure
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
35
48
|
description: The client agent for papermillapp.com
|
36
49
|
email:
|
37
50
|
- ajsharp@gmail.com
|
@@ -47,11 +60,13 @@ files:
|
|
47
60
|
- lib/papermill-agent/configurator.rb
|
48
61
|
- lib/papermill-agent/logger.rb
|
49
62
|
- lib/papermill-agent/rack/collector.rb
|
63
|
+
- lib/papermill-agent/railtie.rb
|
50
64
|
- lib/papermill-agent/response_adapters/base.rb
|
51
65
|
- lib/papermill-agent/response_adapters/rails.rb
|
52
66
|
- lib/papermill-agent/response_adapters/sinatra.rb
|
53
67
|
- lib/papermill-agent/response_parser.rb
|
54
68
|
- lib/papermill-agent/storage.rb
|
69
|
+
- lib/papermill-agent/tasks.rb
|
55
70
|
- lib/papermill-agent.rb
|
56
71
|
- LICENSE
|
57
72
|
- README.md
|