rory 0.6.1 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df765583fa4f315b7f217e8324523d7dd4b3c2ba
4
- data.tar.gz: f977ad97a93566d606319301ea03a06447c2a260
3
+ metadata.gz: 87f7aad3ae233e7193f17c7766db1cc3c551793b
4
+ data.tar.gz: 8c5ac24faba7ab853b087af9dbea31100d966fc8
5
5
  SHA512:
6
- metadata.gz: 3c2dd88f139db2931ebe7fdb9153c6ca4180d65f36622fdc51e18859ae55f6661636fe5c5b525564b9ac6973f9980a2e6266b8508317579f87bce4218b68bf83
7
- data.tar.gz: 152c81f7ab8498553a4d4e1dce0d583c25d29bd882c8c776178e8b73815b3a3f9a08e2ab3e2300b8c95f50d0b2efc034348522125a0089efac00574ebc5c8a4f
6
+ metadata.gz: df8251d088994de0b6333c6ac67daa1c260aa69e7f8aee39f2b59a8234b68cfb096b301664d0c6440e0cf32173160cd5ba4ecd5ee7482d80a3613190148c21c1
7
+ data.tar.gz: 3146ba610b2b6e80178f417bbde883acfdb9a560f57aa85157a2db819d8140137fa8431299bc0e421ae05cf498788896f49d4738a0578de1677c5fe3f09f5c8d
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Gem Version](https://badge.fury.io/rb/rory.svg)](http://badge.fury.io/rb/rory)
1
2
  rory
2
3
  ====
3
4
 
data/lib/rory.rb CHANGED
@@ -2,6 +2,7 @@ ENV['RORY_ENV'] ||= ENV['RACK_ENV'] || 'development'
2
2
 
3
3
  require 'yaml'
4
4
  require 'sequel'
5
+ require 'thread/inheritable_attributes'
5
6
  require 'rack/contrib'
6
7
  require 'rory/application'
7
8
  require 'rory/dispatcher'
@@ -1,5 +1,6 @@
1
1
  require 'pathname'
2
- require 'logger'
2
+ require 'rory/logger'
3
+ require 'rory/request_id'
3
4
  require 'rory/route_mapper'
4
5
  require 'rack/commonlogger'
5
6
  require_relative 'request_parameter_logger'
@@ -39,7 +40,7 @@ module Rory
39
40
  end
40
41
 
41
42
  def root=(root_path)
42
- @root = Pathname.new(root_path).expand_path
43
+ $:.unshift @root = Pathname.new(root_path).realpath
43
44
  end
44
45
  end
45
46
 
@@ -137,6 +138,7 @@ module Rory
137
138
 
138
139
  def use_default_middleware
139
140
  if request_logging_on?
141
+ use_middleware Rory::RequestId, :uuid_prefix => self.class.name
140
142
  use_middleware Rack::PostBodyContentTypeParser
141
143
  use_middleware Rack::CommonLogger, logger
142
144
  use_middleware Rory::RequestParameterLogger, logger, :filters => parameters_to_filter
@@ -11,7 +11,7 @@ module Rory
11
11
 
12
12
  def self.rack_app(app)
13
13
  Proc.new { |env|
14
- new(Rack::Request.new(env), app).dispatch
14
+ new(Rory::Request.new(env), app).dispatch
15
15
  }
16
16
  end
17
17
 
@@ -0,0 +1,46 @@
1
+ require "rory/request"
2
+ require "logger"
3
+
4
+ module Rory
5
+ class Logger < ::Logger
6
+ def initialize(io, options={})
7
+ super(io)
8
+ @default_formatter = Formatter.new
9
+ @tagged = options.fetch(:tagged, [:request_id])
10
+ end
11
+
12
+ def <<(msg)
13
+ super([tagged, msg].reject(&:empty?).join(" "))
14
+ end
15
+
16
+ alias_method :write, :<<
17
+
18
+ def request_id
19
+ Thread.current.get_inheritable_attribute(:rory_request_id)
20
+ end
21
+
22
+ def tagged
23
+ @tagged.map do |key|
24
+ "#{key}=#{quoted_string(public_send(key))}"
25
+ end.join(" ").rstrip
26
+ end
27
+
28
+ private
29
+
30
+ def quoted_string(str)
31
+ str =~ /\s/ ? %["#{str}"] : str
32
+ end
33
+
34
+ def format_message(severity, datetime, progname, msg)
35
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg, tagged)
36
+ end
37
+
38
+ class Formatter < ::Logger::Formatter
39
+ FORMAT = "%s, [%s - %s#%d] %5s -- %s: %s\n"
40
+
41
+ def call(severity, time, progname, msg, tagged)
42
+ FORMAT % [severity[0..0], tagged, format_datetime(time), $$, severity, progname, msg2str(msg)]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module Rory
2
+ class Request < ::Rack::Request
3
+ # Returns the unique request id, which is based off either the X-Request-Id header that can
4
+ # be generated by a firewall, load balancer, or web server or by the RequestId middleware
5
+ # (which sets the rory.request_id environment variable).
6
+ #
7
+ # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
8
+ # This relies on the rack variable set by the Rory::RequestId middleware.
9
+ def uuid
10
+ @uuid ||= env["rory.request_id"]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ module Rory
2
+ # Makes a unique request id available to the rory.request_id env variable (which is then accessible through
3
+ # Rory::Request#uuid) and sends the same id to the client via the X-Request-Id header.
4
+ #
5
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
6
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
7
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
8
+ #
9
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
10
+ # from multiple pieces of the stack.
11
+ class RequestId
12
+ def initialize(app, options={})
13
+ @app = app
14
+ @uuid_creator = options.fetch(:uuid_creator, SecureRandom)
15
+ @uuid_prefix = options[:uuid_prefix]
16
+ end
17
+
18
+ def call(env)
19
+ env["rory.request_id"] = external_request_id(env) || internal_request_id
20
+ Thread.current.set_inheritable_attribute(:rory_request_id, env["rory.request_id"])
21
+ @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["rory.request_id"] }
22
+ end
23
+
24
+ private
25
+
26
+ def external_request_id(env)
27
+ if (request_id = env["HTTP_X_REQUEST_ID"])
28
+ request_id.gsub(/[^\w\-]/, "")[0..254]
29
+ end
30
+ end
31
+
32
+ def internal_request_id
33
+ "#{uuid_prefix}#{@uuid_creator.uuid}"
34
+ end
35
+
36
+ def uuid_prefix
37
+ @uuid_prefix ? "#{Support.tokenize(@uuid_prefix)}-" : ""
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,4 @@
1
1
  require_relative 'parameter_filter'
2
-
3
2
  module Rory
4
3
  class RequestParameterLogger
5
4
 
data/lib/rory/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rory
2
- VERSION = '0.6.1'
2
+ VERSION = '0.7.0'
3
3
  end
data/rory.gemspec CHANGED
@@ -32,14 +32,16 @@ EOF
32
32
  s.add_runtime_dependency 'rack-contrib', '~> 1.2'
33
33
  s.add_runtime_dependency 'sequel', '~> 4.0'
34
34
  s.add_runtime_dependency 'thin', '~> 1.0'
35
+ s.add_runtime_dependency 'thread-inheritable_attributes', '~> 0.1'
35
36
 
36
37
  s.add_development_dependency 'rake', '~> 10.4'
37
38
  s.add_development_dependency 'rspec', '~> 3'
39
+ s.add_development_dependency 'mime-types', '~> 2.6'
38
40
  s.add_development_dependency 'capybara', '~> 2.4'
39
41
  s.add_development_dependency 'yard', '~> 0.8'
40
42
  s.add_development_dependency 'reek', '~> 2.2'
41
43
  s.add_development_dependency 'simplecov', '~> 0.10'
42
- s.add_development_dependency 'bundler', '~> 1.0'
44
+ s.add_development_dependency 'bundler', '~> 1.10'
43
45
  s.add_development_dependency 'pry', '~> 0.10'
44
46
  end
45
47
 
@@ -1,11 +1,38 @@
1
1
  describe Rory::Application do
2
2
  let(:subject) {
3
- Class.new(Rory::Application).tap { |app|
4
- app.root = "whatever"
3
+ Object.const_set(test_rory_app_name, Class.new(Rory::Application).tap { |app|
4
+ app.root = root
5
5
  app.turn_off_request_logging!
6
- }
6
+ })
7
+ Object.const_get(test_rory_app_name)
7
8
  }
8
9
 
10
+ let(:test_rory_app_name){
11
+ "TestRory#{('a'..'z').to_a.sample(5).join}"
12
+ }
13
+ let(:root){"spec/fixture_app"}
14
+
15
+ describe ".root=" do
16
+ let(:root) { "current_app" }
17
+ before { `ln -s spec/fixture_app current_app` }
18
+ after { `rm current_app` }
19
+
20
+ it 'appends to the load path' do
21
+ expect($:).to receive(:unshift).with(Pathname("current_app").realpath)
22
+ subject
23
+ end
24
+ end
25
+
26
+ describe ".root" do
27
+ let(:root) { "current_app" }
28
+ before { `ln -s spec/fixture_app current_app` }
29
+ after { `rm current_app` }
30
+
31
+ it "returns the realpath" do
32
+ expect(subject.root.to_s).to match(/spec\/fixture_app/)
33
+ end
34
+ end
35
+
9
36
  describe ".configure" do
10
37
  it 'yields the given block to self' do
11
38
  subject.configure do |c|
@@ -156,6 +183,7 @@ describe Rory::Application do
156
183
  allow(subject.instance).to receive(:request_logging_on?).and_return(true)
157
184
  allow(subject.instance).to receive(:parameters_to_filter).and_return([:horses])
158
185
  allow(subject.instance).to receive(:logger).and_return(:the_logger)
186
+ expect(subject.instance).to receive(:use_middleware).with(Rory::RequestId, :uuid_prefix => test_rory_app_name)
159
187
  expect(subject.instance).to receive(:use_middleware).with(Rack::PostBodyContentTypeParser)
160
188
  expect(subject.instance).to receive(:use_middleware).with(Rack::CommonLogger, :the_logger)
161
189
  expect(subject.instance).to receive(:use_middleware).with(Rory::RequestParameterLogger, :the_logger, :filters => [:horses])
@@ -263,4 +291,4 @@ describe Rory::Application do
263
291
  end
264
292
  end
265
293
  end
266
- end
294
+ end
@@ -0,0 +1,90 @@
1
+ require "logger"
2
+ require "thread/inheritable_attributes"
3
+ require "rory/logger"
4
+
5
+ describe Rory::Logger do
6
+ subject { described_class.new(string_io) }
7
+ let(:string_io) { StringIO.new }
8
+ let(:result) { string_io.tap(&:rewind).read }
9
+
10
+ let(:simple_format) {
11
+ subject.formatter = Proc.new do |severity, _, _, msg, tagged|
12
+ "#{severity} #{tagged} - #{msg}"
13
+ end
14
+ }
15
+
16
+ let(:rory_request_id) { "1111-2222" }
17
+
18
+ before { Thread.current[:inheritable_attributes] = {:rory_request_id => rory_request_id} }
19
+ after { Thread.current[:inheritable_attributes] = nil }
20
+
21
+ context "when tagged is empty" do
22
+ subject { described_class.new(string_io, tagged: []) }
23
+ it "does not tag anything" do
24
+ simple_format
25
+ subject.<< "Hello"
26
+ expect(result).to eq "Hello"
27
+ end
28
+ end
29
+
30
+ context "creating custom tags" do
31
+ subject { described_class.new(string_io, tagged: [:custom_tag, :request_id]) }
32
+ it "needs an instance method go along with new tag" do
33
+ def subject.custom_tag
34
+ "Words.."
35
+ end
36
+ simple_format
37
+ subject.<< "Hello"
38
+ expect(result).to eq "custom_tag=Words.. request_id=1111-2222 Hello"
39
+ end
40
+ end
41
+
42
+ describe "#<<" do
43
+ it "tags are present with this form" do
44
+ simple_format
45
+ subject.<< "Hello"
46
+ expect(result).to eq "request_id=1111-2222 Hello"
47
+ end
48
+ end
49
+
50
+ context "when a tagged values has spaces" do
51
+ let(:rory_request_id) { "1111 2222" }
52
+ it "is quoted" do
53
+ simple_format
54
+ subject.<< "Good Morning"
55
+ expect(result).to eq 'request_id="1111 2222" Good Morning'
56
+ end
57
+ end
58
+
59
+ describe "#info" do
60
+ it "severity level is set to INFO" do
61
+ simple_format
62
+ subject.info "Hello"
63
+ expect(result).to eq "INFO request_id=1111-2222 - Hello"
64
+ end
65
+ end
66
+
67
+ describe "#formatter" do
68
+ it "define a custom formatting" do
69
+ subject.formatter = Proc.new do |_, _, _, msg, tagged|
70
+ "This is formatted: #{tagged} - #{msg}"
71
+ end
72
+ subject.info "Hello"
73
+ expect(result).to eq "This is formatted: request_id=1111-2222 - Hello"
74
+ end
75
+
76
+ it "has default formatting" do
77
+ subject.info "Goodbye"
78
+ expect(result).to match /request_id=1111-2222.*INFO -- : Goodbye\n/
79
+ end
80
+ end
81
+
82
+ describe "integration with Rack::CommonLogger" do
83
+ it "only prepends tags" do
84
+ [200, { "REMOTE_ADDR" => "127.0.0.1", "HTTP_VERSION" => "1.1" }, ""]
85
+ Rack::CommonLogger.new(Proc.new { |a| a }, subject).send(:log, { "REMOTE_ADDR" => "127.0.0.1", "HTTP_VERSION" => "1.1", Rack::QUERY_STRING => "abc" }, 200, {}, 1)
86
+ "I, [1111-2222 - 2016-01-20T16:30:52.193516 #5341] INFO -- : 127.0.0.1 - - [20/Jan/2016:16:30:52 -0800] \" ?abc 1.1\" 200 - 1453336251.1934\n\n"
87
+ expect(result).to match /request_id=1111-2222 127.0.0.1 - - /
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,56 @@
1
+ RSpec.describe Rory::RequestId do
2
+ subject { described_class.new(Proc.new {|env|[200, headers, ""] },
3
+ uuid_prefix: uuid_prefix,
4
+ uuid_creator: class_double(SecureRandom, uuid: "1234")) }
5
+ after { Thread.current[:inheritable_attributes] = nil }
6
+ let(:headers) { {} }
7
+ let(:env) { {} }
8
+ let(:uuid_prefix) { nil }
9
+
10
+ context "when no external_request_id is set" do
11
+ before { subject.call(env) }
12
+
13
+ it "sets env['rory.request_id']" do
14
+ expect(env["rory.request_id"]).to eq "1234"
15
+ end
16
+
17
+ it "sets header['X-Request-Id']" do
18
+ expect(headers["X-Request-Id"]).to eq "1234"
19
+ end
20
+
21
+ it "sets Thread.current[:rory_request_id]" do
22
+ expect(Thread.current.get_inheritable_attribute(:rory_request_id)).to eq "1234"
23
+ end
24
+
25
+ context "the uuid can be given a prefixed to know where it was created" do
26
+ let(:uuid_prefix) { "AppName" }
27
+ it { expect(Thread.current.get_inheritable_attribute(:rory_request_id)).to eq "app_name-1234" }
28
+ end
29
+ end
30
+
31
+ context "when external_request_id is set" do
32
+ before { subject.call(env) }
33
+ let(:env) { { "HTTP_X_REQUEST_ID" => "4321" } }
34
+
35
+ it "sets env['rory.request_id']" do
36
+ expect(env["rory.request_id"]).to eq "4321"
37
+ end
38
+
39
+ it "sets header['X-Request-Id']" do
40
+ expect(headers["X-Request-Id"]).to eq "4321"
41
+ end
42
+
43
+ it "sets Thread.current[:rory_request_id]" do
44
+ expect(Thread.current.get_inheritable_attribute(:rory_request_id)).to eq "4321"
45
+ end
46
+ end
47
+
48
+ context "use default SecureRandom" do
49
+ subject { described_class.new(Proc.new {|env|[200, headers, ""] },
50
+ uuid_prefix: uuid_prefix).call({}) }
51
+ it "call uuid on SecureRandom" do
52
+ expect(SecureRandom).to receive(:uuid).once
53
+ subject
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.describe Rory::Request do
2
+ describe "#uuid" do
3
+ it "returns the value set by env['rory.request_id']" do
4
+ env = { "rory.request_id" => "uuid-from_rory_request" }
5
+ expect(described_class.new(env).uuid).to eq "uuid-from_rory_request"
6
+ end
7
+
8
+ context "when no key exists" do
9
+ it "returns nil" do
10
+ expect(described_class.new({}).uuid).to eq nil
11
+ end
12
+ end
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ravi Gadad
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-06-18 00:00:00.000000000 Z
13
+ date: 2016-01-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -68,6 +68,20 @@ dependencies:
68
68
  - - "~>"
69
69
  - !ruby/object:Gem::Version
70
70
  version: '1.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: thread-inheritable_attributes
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '0.1'
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '0.1'
71
85
  - !ruby/object:Gem::Dependency
72
86
  name: rake
73
87
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +110,20 @@ dependencies:
96
110
  - - "~>"
97
111
  - !ruby/object:Gem::Version
98
112
  version: '3'
113
+ - !ruby/object:Gem::Dependency
114
+ name: mime-types
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '2.6'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '2.6'
99
127
  - !ruby/object:Gem::Dependency
100
128
  name: capybara
101
129
  requirement: !ruby/object:Gem::Requirement
@@ -158,14 +186,14 @@ dependencies:
158
186
  requirements:
159
187
  - - "~>"
160
188
  - !ruby/object:Gem::Version
161
- version: '1.0'
189
+ version: '1.10'
162
190
  type: :development
163
191
  prerelease: false
164
192
  version_requirements: !ruby/object:Gem::Requirement
165
193
  requirements:
166
194
  - - "~>"
167
195
  - !ruby/object:Gem::Version
168
- version: '1.0'
196
+ version: '1.10'
169
197
  - !ruby/object:Gem::Dependency
170
198
  name: pry
171
199
  requirement: !ruby/object:Gem::Requirement
@@ -201,10 +229,13 @@ files:
201
229
  - lib/rory/application.rb
202
230
  - lib/rory/controller.rb
203
231
  - lib/rory/dispatcher.rb
232
+ - lib/rory/logger.rb
204
233
  - lib/rory/parameter_filter.rb
205
234
  - lib/rory/path_generation.rb
206
235
  - lib/rory/renderer.rb
207
236
  - lib/rory/renderer/context.rb
237
+ - lib/rory/request.rb
238
+ - lib/rory/request_id.rb
208
239
  - lib/rory/request_parameter_logger.rb
209
240
  - lib/rory/route.rb
210
241
  - lib/rory/route_mapper.rb
@@ -237,10 +268,13 @@ files:
237
268
  - spec/lib/rory/application_spec.rb
238
269
  - spec/lib/rory/controller_spec.rb
239
270
  - spec/lib/rory/dispatcher_spec.rb
271
+ - spec/lib/rory/logger_spec.rb
240
272
  - spec/lib/rory/parameter_filter_spec.rb
241
273
  - spec/lib/rory/renderer/context_spec.rb
242
274
  - spec/lib/rory/renderer_spec.rb
275
+ - spec/lib/rory/request_id_spec.rb
243
276
  - spec/lib/rory/request_parameter_logger_spec.rb
277
+ - spec/lib/rory/request_spec.rb
244
278
  - spec/lib/rory/route_spec.rb
245
279
  - spec/lib/rory/support_spec.rb
246
280
  - spec/lib/rory_spec.rb
@@ -267,7 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
301
  version: 1.3.6
268
302
  requirements: []
269
303
  rubyforge_project:
270
- rubygems_version: 2.2.2
304
+ rubygems_version: 2.2.5
271
305
  signing_key:
272
306
  specification_version: 4
273
307
  summary: Another Ruby web framework. Just what the world needs.