qs-request-tracker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 2.0.0
6
+ - jruby-19mode
7
+ - rbx-19mode
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 0.0.1 / 2013-08-12
2
+
3
+ * Adds travis badge
4
+ * Adds travis configuration
5
+ * The beginning
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in qs-request-tracker.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Thorben Schröder
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Qs::Request::Tracker
2
+
3
+ [![Build Status](https://travis-ci.org/quarter-spiral/qs-request-tracker.png)](https://travis-ci.org/quarter-spiral/qs-request-tracker)
4
+
5
+ Manages a ``Request-Id`` for each HTTP request. Hooks transparently into [Faraday](https://github.com/lostisland/faraday) and [service-client](https://github.com/quarter-spiral/service-client) to pass request ids on to subsequent calls to other services. This way errors can be tracked through all layers and apps of your infrastructure easily.
6
+
7
+ ## Use as a Rack middleware
8
+
9
+ The easiest way to add request ids to your system is to add the ``Qs::Request::Tracker::Middleware`` as a middleware to your app.
10
+
11
+ ```ruby
12
+ require 'qs/request/tracker'
13
+
14
+ Rack::Builder.new do
15
+ use Qs::Request::Tracker::Middleware
16
+ run YourApp.new
17
+ end
18
+ ```
19
+
20
+ This will add the request id to the response. If there is no ``Request-Id`` HTTP header set in the request it will also set that. Please note that all middlewares that are included before it will not have that header set!
21
+
22
+ ## Use the Faraday middleware
23
+
24
+ If you are using [Faraday](https://github.com/lostisland/faraday) you transparently add the request id to all outgoing HTTP calls, too.
25
+
26
+ ```ruby
27
+ require 'faraday'
28
+ # make sure to require this middleware after faraday itself
29
+ require 'qs/request/tracker/faraday_middleware'
30
+
31
+ conn = Faraday.new(:url => 'http://example.com') do |faraday|
32
+ # it is important to have the request_id middleware inserted before any Faraday adapter
33
+ faraday.request :request_id
34
+ end
35
+ ```
36
+
37
+ All requests done by that connection will now have a ``Request-Id`` http header set that equal the one from the request that came into your system. If this is used not within a request context that header will just not get set.
38
+
39
+ **Imporant:** This feature relies on [thread local variables](http://www.ruby-doc.org/core-2.0/Thread.html#label-Fiber-local+vs.+Thread-local) and will not work if you deal with more than one request per thread at a time!
40
+
41
+ This approach is inspired by [Andy](https://github.com/adelcambre)'s [talk](http://www.youtube.com/watch?v=NpTT30wLL-w) on debugging large scale systems.
42
+
43
+ ## Use the service-client extension
44
+
45
+ This extension will automatically use the Faraday middleware for [service-client](https://github.com/quarter-spiral/service-client) connections that use the ``Service::Client::Adapter::Faraday`` adapter.
46
+
47
+ ```ruby
48
+ require 'service-client'
49
+ # make sure to require this extension after service-client itself
50
+ require 'qs/request/tracker/service_client_extension'
51
+
52
+ client = Service::Client.new('http://example.com/')
53
+ client.urls.add(:root, :get, "/")
54
+ client.get(client.urls.root, oauth_token)
55
+ ```
56
+
57
+ This will then heave the ``Request-Id`` http header set through the Faraday middleware.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,43 @@
1
+ if defined? Faraday::Middleware
2
+ module Qs::Request::Tracker
3
+ class FaradayMiddleware < Faraday::Middleware
4
+ THREAD_LOCAL_MISMATCH_MESSAGES = "REQUEST TRACKING ERROR! Found an ID in the headers: %s but a different one in the thread locals: %s"
5
+
6
+ def initialize(app, options = {})
7
+ super(app)
8
+ end
9
+
10
+ def call(env)
11
+ annotate_env_with_request_id!(env) if thread_local_request_id
12
+ @app.call(env)
13
+ end
14
+
15
+ protected
16
+ def annotate_env_with_request_id!(env)
17
+ log_request_mismatch_error(env) unless request_ids_match?(env)
18
+ env[:request_headers][HTTP_HEADER_FIELD] = thread_local_request_id
19
+ end
20
+
21
+ def request_ids_match?(env)
22
+ return true unless headers_request_id(env)
23
+ (thread_local_request_id && thread_local_request_id == headers_request_id(env))
24
+ end
25
+
26
+ def thread_local_request_id
27
+ Qs::Request::Tracker.thread_request_id
28
+ end
29
+
30
+ def headers_request_id(env)
31
+ env[:request_headers][HTTP_HEADER_FIELD]
32
+ end
33
+
34
+ def log_request_mismatch_error(env)
35
+ STDERR.puts(THREAD_LOCAL_MISMATCH_MESSAGES % [thread_local_request_id.inspect, headers_request_id(env).inspect])
36
+ end
37
+ end
38
+ end
39
+
40
+ if Faraday.respond_to? :register_middleware
41
+ Faraday.register_middleware :request, :request_id => lambda { Qs::Request::Tracker::FaradayMiddleware }
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ require 'uuid'
2
+
3
+ module Qs
4
+ module Request
5
+ module Tracker
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env['HTTP_REQUEST_ID'] ||= UUID.new.generate
13
+ Qs::Request::Tracker.thread_request_id = env['HTTP_REQUEST_ID']
14
+ status, headers, body = @app.call(env)
15
+ headers[HTTP_HEADER_FIELD] ||= Qs::Request::Tracker.thread_request_id
16
+ [status, headers, body]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ if defined? Service::Client::Adapter::Faraday
2
+ require 'qs/request/tracker/faraday_middleware'
3
+
4
+ class Service::Client::Adapter::Faraday
5
+ alias _before_qs_request_tracker_service_client_extension_initialize initialize
6
+ def initialize(*args)
7
+ _before_qs_request_tracker_service_client_extension_initialize(*args)
8
+ existing_builder = @builder
9
+ @builder = lambda do |faraday|
10
+ faraday.request :request_id
11
+ existing_builder ? existing_builder.call(faraday) : true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Qs
2
+ module Request
3
+ module Tracker
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require "qs/request/tracker/version"
2
+ require "qs/request/tracker/middleware"
3
+
4
+ module Qs
5
+ module Request
6
+ module Tracker
7
+ THREAD_LOCAL_REQUEST_ID_KEY = :'_HTTP_REQUEST_ID'
8
+ HTTP_HEADER_FIELD = 'Request-Id'
9
+
10
+ def self.thread_request_id
11
+ Thread.current[THREAD_LOCAL_REQUEST_ID_KEY]
12
+ end
13
+
14
+ def self.thread_request_id=(new_id)
15
+ Thread.current[THREAD_LOCAL_REQUEST_ID_KEY] = new_id
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'qs/request/tracker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "qs-request-tracker"
8
+ spec.version = Qs::Request::Tracker::VERSION
9
+ spec.authors = ["Thorben Schröder"]
10
+ spec.email = ["stillepost@gmail.com"]
11
+ spec.description = %q{Manages a Request-Id for each HTTP request}
12
+ spec.summary = %q{Manages a Request-Id for each HTTP request}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "faraday"
24
+ spec.add_development_dependency "service-client", ">= 0.0.14"
25
+ spec.add_development_dependency "sinatra"
26
+ spec.add_development_dependency "rack-client"
27
+ spec.add_development_dependency "rack-test"
28
+
29
+ spec.add_dependency 'uuid'
30
+ end
@@ -0,0 +1,59 @@
1
+ require 'sinatra'
2
+ disable :run
3
+ require 'faraday'
4
+ require 'rack/client'
5
+
6
+ require_relative './spec_helper'
7
+
8
+ require 'qs/request/tracker/faraday_middleware'
9
+
10
+ ALL_REQUESTS = []
11
+
12
+ class SampleAppFaraday < Sinatra::Base
13
+ helpers do
14
+ def request_with_faraday!
15
+ conn = Faraday.new(:url => 'http://example.com') do |faraday|
16
+ faraday.request :request_id
17
+ faraday.adapter :rack, SampleAppFaraday.new
18
+ end
19
+
20
+ conn.get '/by-client'
21
+
22
+ 'done'
23
+ end
24
+ end
25
+
26
+ get "/by-client" do
27
+ ALL_REQUESTS << request
28
+ '{"done": true}'
29
+ end
30
+
31
+ get "/with-request-id-set" do
32
+ Qs::Request::Tracker.thread_request_id = '123'
33
+
34
+ request_with_faraday!
35
+ end
36
+
37
+ get "/without-request-id-set" do
38
+ request_with_faraday!
39
+ end
40
+ end
41
+
42
+ describe Qs::Request::Tracker do
43
+ before do
44
+ Qs::Request::Tracker.thread_request_id = nil
45
+ @client = Rack::Client.new do
46
+ run SampleAppFaraday.new
47
+ end
48
+ end
49
+
50
+ it "passes the request-id as a http header" do
51
+ @client.get('/with-request-id-set')
52
+ ALL_REQUESTS.last.env['HTTP_REQUEST_ID'].must_equal '123'
53
+ end
54
+
55
+ it "doesn't add a request-id if there is none present" do
56
+ @client.get('/without-request-id-set')
57
+ ALL_REQUESTS.last.env['HTTP_REQUEST_ID'].must_be_nil
58
+ end
59
+ end
@@ -0,0 +1,51 @@
1
+ require_relative './spec_helper'
2
+
3
+ require 'sinatra'
4
+ disable :run
5
+
6
+ require 'rack/client'
7
+
8
+ require 'json'
9
+
10
+ class SampleAppMiddleware < Sinatra::Base
11
+ use Qs::Request::Tracker::Middleware
12
+
13
+ get '/' do
14
+ JSON.dump('request_id' => request.env['HTTP_REQUEST_ID'])
15
+ end
16
+ end
17
+
18
+ def parse_request_id(response)
19
+ JSON.parse(response.body)['request_id']
20
+ end
21
+
22
+ describe Qs::Request::Tracker::Middleware do
23
+ before do
24
+ @client = Rack::Client.new do
25
+ run SampleAppMiddleware.new
26
+ end
27
+ Qs::Request::Tracker.thread_request_id = nil
28
+ end
29
+
30
+ it "adds a request id if none is provided via an HTTP request header" do
31
+ response = @client.get '/'
32
+ request_id = parse_request_id(response)
33
+ request_id.wont_be_nil
34
+ end
35
+
36
+ it "sets the thread local id" do
37
+ response = @client.get '/'
38
+ Qs::Request::Tracker.thread_request_id.wont_be_nil
39
+ end
40
+
41
+ it "sets the request id in the response headers" do
42
+ response = @client.get '/'
43
+ response.headers['Request-Id'].wont_be_nil
44
+ end
45
+
46
+ it "does not change the supplied request id" do
47
+ response = @client.get '/', {'Request-Id' => '12345'}
48
+ Qs::Request::Tracker.thread_request_id.must_equal '12345'
49
+ Qs::Request::Tracker.thread_request_id.must_equal '12345'
50
+ end
51
+ end
@@ -0,0 +1,59 @@
1
+ require 'sinatra'
2
+ disable :run
3
+
4
+ require 'service-client'
5
+ require 'rack/client'
6
+
7
+ require_relative './spec_helper'
8
+
9
+ require 'qs/request/tracker/service_client_extension'
10
+
11
+ ALL_REQUESTS = []
12
+
13
+ class SampleAppServiceClient < Sinatra::Base
14
+ helpers do
15
+ def request_with_service_client!
16
+ client = Service::Client.new('http://example.com/')
17
+ client.urls.add(:test, :get, "/by-client")
18
+ client.raw.adapter = Service::Client::Adapter::Faraday.new(adapter: [:rack, SampleAppServiceClient.new])
19
+
20
+ client.get(client.urls.test, 'no-token')
21
+
22
+ 'done'
23
+ end
24
+ end
25
+
26
+ get "/by-client" do
27
+ ALL_REQUESTS << request
28
+ '{"done": true}'
29
+ end
30
+
31
+ get "/with-request-id-set" do
32
+ Qs::Request::Tracker.thread_request_id = '123'
33
+
34
+ request_with_service_client!
35
+ end
36
+
37
+ get "/without-request-id-set" do
38
+ request_with_service_client!
39
+ end
40
+ end
41
+
42
+ describe Qs::Request::Tracker do
43
+ before do
44
+ Qs::Request::Tracker.thread_request_id = nil
45
+ @client = Rack::Client.new do
46
+ run SampleAppServiceClient.new
47
+ end
48
+ end
49
+
50
+ it "passes the request-id as a http header" do
51
+ @client.get('/with-request-id-set')
52
+ ALL_REQUESTS.last.env['HTTP_REQUEST_ID'].must_equal '123'
53
+ end
54
+
55
+ it "doesn't add a request-id if there is none present" do
56
+ @client.get('/without-request-id-set')
57
+ ALL_REQUESTS.last.env['HTTP_REQUEST_ID'].must_be_nil
58
+ end
59
+ end
@@ -0,0 +1,7 @@
1
+ ENV['RACK_ENV'] ||= 'test'
2
+
3
+ Bundler.require
4
+
5
+ require 'qs/request/tracker'
6
+
7
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qs-request-tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thorben Schröder
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: faraday
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: service-client
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.0.14
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.0.14
78
+ - !ruby/object:Gem::Dependency
79
+ name: sinatra
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rack-client
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack-test
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: uuid
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: Manages a Request-Id for each HTTP request
143
+ email:
144
+ - stillepost@gmail.com
145
+ executables: []
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - .gitignore
150
+ - .travis.yml
151
+ - CHANGELOG.md
152
+ - Gemfile
153
+ - LICENSE.txt
154
+ - README.md
155
+ - Rakefile
156
+ - lib/qs/request/tracker.rb
157
+ - lib/qs/request/tracker/faraday_middleware.rb
158
+ - lib/qs/request/tracker/middleware.rb
159
+ - lib/qs/request/tracker/service_client_extension.rb
160
+ - lib/qs/request/tracker/version.rb
161
+ - qs-request-tracker.gemspec
162
+ - spec/faraday_middleware_spec.rb
163
+ - spec/middleware_spec.rb
164
+ - spec/service_client_extension_spec.rb
165
+ - spec/spec_helper.rb
166
+ homepage: ''
167
+ licenses:
168
+ - MIT
169
+ post_install_message:
170
+ rdoc_options: []
171
+ require_paths:
172
+ - lib
173
+ required_ruby_version: !ruby/object:Gem::Requirement
174
+ none: false
175
+ requirements:
176
+ - - ! '>='
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ! '>='
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 1.8.25
188
+ signing_key:
189
+ specification_version: 3
190
+ summary: Manages a Request-Id for each HTTP request
191
+ test_files:
192
+ - spec/faraday_middleware_spec.rb
193
+ - spec/middleware_spec.rb
194
+ - spec/service_client_extension_spec.rb
195
+ - spec/spec_helper.rb