e20_ops_middleware 2.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,4 @@
1
+ .bundle
2
+ vendor
3
+ *.gemspec
4
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubyforge
2
+
3
+ gem "jeweler", "~> 1.4.0"
4
+ gem "rspec", "~> 1.3.0"
5
+ gem "rake", "~> 0.8.7"
6
+ gem "uuid", "~> 2.1.0"
7
+ gem "activesupport", "~> 2.3.8"
8
+ gem "json_pure", "= 1.4.3"
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (2.3.8)
5
+ gemcutter (0.6.1)
6
+ git (1.2.5)
7
+ jeweler (1.4.0)
8
+ gemcutter (>= 0.1.0)
9
+ git (>= 1.2.5)
10
+ rubyforge (>= 2.0.0)
11
+ json_pure (1.4.3)
12
+ macaddr (1.0.0)
13
+ rake (0.8.7)
14
+ rspec (1.3.0)
15
+ rubyforge (2.0.4)
16
+ json_pure (>= 1.1.7)
17
+ uuid (2.1.1)
18
+ macaddr (~> 1.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ activesupport (~> 2.3.8)
25
+ jeweler (~> 1.4.0)
26
+ json_pure (= 1.4.3)
27
+ rake (~> 0.8.7)
28
+ rspec (~> 1.3.0)
29
+ uuid (~> 2.1.0)
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 2.0.1 / 2010-08-29
2
+
3
+ * 1 major enhancement
4
+
5
+ * First public release!
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ Efficiency 2.0 Ops Middleware
2
+ =============================
3
+
4
+ A collection of useful middleware for exposing information about deployed Rack
5
+ applications. Efficiency 2.0 uses this to track distributed transactions
6
+ across its Ruby-based service oriented architecture.
7
+
8
+ Features
9
+ --------
10
+
11
+ * Adds a `X-Served-By` header with the hostname of the server that processed
12
+ the request.
13
+ * Adds a `X-Transaction` header with a unique ID for the web request.
14
+ * Adds a `X-Revision` header with the running Git revision.
15
+ * Adds an endpoint of `/system/revision` for easily checking the running
16
+ revision. This can be leveraged in a post-deployment sanity check to ensure
17
+ the application servers restarted properly.
18
+
19
+ Install
20
+ -------
21
+
22
+ ### Rails ###
23
+
24
+ Add to your `Gemfile`:
25
+
26
+ gem "e20_ops_middleware"
27
+
28
+ Install the gem:
29
+
30
+ $ bundle install
31
+
32
+ Create a `config/initializers/ops_middleware.rb` with the following:
33
+
34
+ ActionController::Dispatcher.middleware.with_options :logger => Rails.logger do |m|
35
+ m.use E20::Ops::Middleware::RevisionMiddleware
36
+ m.use E20::Ops::Middleware::HostnameMiddleware
37
+ m.use E20::Ops::Middleware::TransactionIdMiddleware
38
+ end
39
+
40
+ ### Rack ###
41
+
42
+ In `config.ru`, add:
43
+
44
+ use E20::Ops::Middleware::RevisionMiddleware
45
+ use E20::Ops::Middleware::HostnameMiddleware
46
+ use E20::Ops::Middleware::TransactionIdMiddleware
47
+
48
+ Usage
49
+ -----
50
+
51
+ The information exposed by the middleware can be viewed manually with `curl`
52
+ and will also be logged to the provided logger (or STDOUT). Additionally,
53
+ we've found it useful to log this information when receiving responses from
54
+ REST web services.
55
+
56
+ ### Revision Middleware ###
57
+
58
+ Revisions can be queried directly by using the `/system/revision` endpoint:
59
+
60
+ $ curl http://instance/system/revision
61
+ fe09f24b4a927b6eab5db66b6a89fe960e2ff03b
62
+
63
+ The current revision will be passed as an HTTP header for other requests:
64
+
65
+ $ curl -I http://instance/
66
+ HTTP/1.1 200 OK
67
+ Content-Type: text/html; charset=utf-8
68
+ Content-Length: 3304
69
+ X-Revision: fe09f24b4a927b6eab5db66b6a89fe960e2ff03b
70
+
71
+ The current revision will also be logged upon application start:
72
+
73
+ $ grep RevisionMiddleware log/production.log
74
+ [E20::Ops::Middleware::RevisionMiddleware] Running: fe09f24b4a927b6eab5db66b6a89fe960e2ff03b
75
+
76
+ ### Hostname Middleware ###
77
+
78
+ The hostname of the system that processed the request will be passed as an
79
+ HTTP header:
80
+
81
+ $ curl -I http://instance/
82
+ HTTP/1.1 200 OK
83
+ Content-Type: text/html; charset=utf-8
84
+ Content-Length: 3304
85
+ X-Served-By: fulton
86
+
87
+ The hostname will also be logged upon application start:
88
+
89
+ $ grep HostnameMiddleware log/production.log
90
+ [E20::Ops::Middleware::HostnameMiddleware] Running on: fulton
91
+
92
+ ### Transaction ID Middleware ###
93
+
94
+ A transaction ID will be logged for each incoming request:
95
+
96
+ $ grep TransactionIdMiddleware log/production.log
97
+ [E20::Ops::Middleware::TransactionIdMiddleware] Transaction ID: 111d3180-91f4-012d-ce1a-549a20d01d99
98
+
99
+ The transaction ID will also be passed as an HTTP header:
100
+
101
+ $ curl -I http://instance/
102
+ HTTP/1.1 200 OK
103
+ Content-Type: text/html; charset=utf-8
104
+ Content-Length: 3304
105
+ X-Transaction: 111d3180-91f4-012d-ce1a-549a20d01d99
106
+
107
+ Thanks
108
+ ------
109
+
110
+ Thanks to Efficiency 2.0 ([http://efficiency20.com](http://efficiency20.com))
111
+ for sponsoring development of this gem.
112
+
113
+ Development
114
+ -----------
115
+
116
+ To run the tests:
117
+
118
+ $ bundle install
119
+ $ rake
120
+
121
+ License
122
+ -------
123
+
124
+ (The MIT License)
125
+
126
+ Copyright © 2010 Efficiency 2.0, LLC
127
+
128
+ Permission is hereby granted, free of charge, to any person obtaining a copy
129
+ of this software and associated documentation files (the ‘Software’), to deal
130
+ in the Software without restriction, including without limitation the rights
131
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
132
+ copies of the Software, and to permit persons to whom the Software is
133
+ furnished to do so, subject to the following conditions:
134
+
135
+ The above copyright notice and this permission notice shall be included in all
136
+ copies or substantial portions of the Software.
137
+
138
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
139
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
140
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
141
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
142
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
143
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
144
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup
4
+
5
+ require 'jeweler'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "e20_ops_middleware"
9
+ gem.summary = "Collection of useful middleware for exposing information about deployed Rack applications"
10
+ gem.email = "tech@efficiency20.com"
11
+ gem.homepage = "http://github.com/efficiency20/ops_middleware"
12
+ gem.description = "Adds middleware for debugging purposes"
13
+ gem.authors = ["Efficiency 2.0"]
14
+ gem.add_dependency "uuid", "~> 2.1.0"
15
+ gem.add_development_dependency "rspec", "~> 1.3.0"
16
+ end
17
+
18
+ require "spec/rake/spectask"
19
+
20
+ desc "Run all specs"
21
+ Spec::Rake::SpecTask.new("spec") do |t|
22
+ t.spec_files = FileList["spec/**/*_spec.rb"]
23
+ end
24
+
25
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.0.1
data/ci.rb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ["bundle install vendor/bundle",
4
+ "rake"
5
+ ].each do |stage|
6
+ exit 1 unless system(stage)
7
+ end
@@ -0,0 +1,11 @@
1
+ module E20
2
+ module Ops
3
+ class Hostname
4
+
5
+ def to_s
6
+ @hostname ||= `hostname`.strip
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module E20
2
+ module Ops
3
+ module Middleware
4
+ class HostnameMiddleware
5
+
6
+ def initialize(app, options = {})
7
+ @app = app
8
+ @hostname = options[:hostname] || Hostname.new
9
+
10
+ if (logger = options[:logger])
11
+ logger.info "[#{self.class.name}] Running on: #{@hostname}"
12
+ end
13
+ end
14
+
15
+ def call(env)
16
+ status, headers, body = @app.call(env)
17
+ headers["X-Served-By"] = @hostname.to_s
18
+ [status, headers, body]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ module E20
2
+ module Ops
3
+ module Middleware
4
+ class RevisionMiddleware
5
+
6
+ def initialize(app, options = {})
7
+ @app = app
8
+ @revision = options[:revision] || Revision.new
9
+
10
+ if (logger = options[:logger])
11
+ logger.info "[#{self.class.name}] Running: #{@revision}"
12
+ end
13
+ end
14
+
15
+ def call(env)
16
+ if env["PATH_INFO"] == "/system/revision"
17
+ body = "#{@revision}\n"
18
+ [200, { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s }, body]
19
+ else
20
+ status, headers, body = @app.call(env)
21
+ headers["X-Revision"] = @revision.to_s
22
+ [status, headers, body]
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ require "uuid"
2
+ require "logger"
3
+
4
+ module E20
5
+ module Ops
6
+ module Middleware
7
+ class TransactionIdMiddleware
8
+
9
+ def initialize(app, options = {})
10
+ @app = app
11
+ @uuid_generator = options[:uuid_generator] || UUID.new
12
+ @logger = options[:logger] || Logger.new(STDOUT)
13
+ end
14
+
15
+ def call(env)
16
+ uuid = @uuid_generator.generate
17
+ @logger.info "[#{self.class.name}] Transaction ID: #{uuid}"
18
+
19
+ status, headers, body = @app.call(env)
20
+ headers["X-Transaction"] = uuid
21
+ [status, headers, body]
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module E20
2
+ module Ops
3
+ autoload :Revision, "e20/ops/revision"
4
+ autoload :Hostname, "e20/ops/hostname"
5
+
6
+ module Middleware
7
+ autoload :HostnameMiddleware, "e20/ops/middleware/hostname_middleware"
8
+ autoload :RevisionMiddleware, "e20/ops/middleware/revision_middleware"
9
+ autoload :TransactionIdMiddleware, "e20/ops/middleware/transaction_id_middleware"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ require "active_support"
2
+
3
+ module E20
4
+ module Ops
5
+ class Revision
6
+
7
+ def initialize(root = Pathname.new(Dir.pwd))
8
+ @root = root
9
+ end
10
+
11
+ def to_s
12
+ @revision ||= begin
13
+ if revision_file.exist?
14
+ revision_file.read.strip
15
+ elsif revision_from_git.present?
16
+ revision_from_git
17
+ else
18
+ "unknown"
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def revision_from_git
26
+ @revision_from_git ||= `git rev-parse HEAD`.strip
27
+ end
28
+
29
+ def revision_file
30
+ @root.join("REVISION")
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ describe E20::Ops::Hostname do
4
+ it "returns the hostname" do
5
+ hostname = E20::Ops::Hostname.new
6
+ hostname.should_receive(:`).with("hostname").and_return("Computer.local\n")
7
+ hostname.to_s.should == "Computer.local"
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe E20::Ops::Middleware::HostnameMiddleware do
4
+ let(:app) { Proc.new { |env| [200, {}, "OK!"] } }
5
+
6
+ it "is initialized with an app" do
7
+ E20::Ops::Middleware::HostnameMiddleware.new(app)
8
+ end
9
+
10
+ it "delegates to the app" do
11
+ middleware = E20::Ops::Middleware::HostnameMiddleware.new(app)
12
+ status, headers, body = middleware.call({})
13
+ body.should == "OK!"
14
+ end
15
+
16
+ it "logs the hostname when initialized" do
17
+ log_io = StringIO.new
18
+ E20::Ops::Middleware::HostnameMiddleware.new(app, :logger => Logger.new(log_io))
19
+ log_io.string.should include("[E20::Ops::Middleware::HostnameMiddleware] Running on: ")
20
+ end
21
+
22
+ it "sets an X-Served-By header" do
23
+ middleware = E20::Ops::Middleware::HostnameMiddleware.new(app, :hostname => "Computer.local")
24
+ status, headers, body = middleware.call({})
25
+ headers["X-Served-By"].should == "Computer.local"
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe E20::Ops::Middleware::RevisionMiddleware do
4
+ let(:app) { Proc.new { |env| [200, {}, "OK!"] } }
5
+
6
+ it "is initialized with an app" do
7
+ E20::Ops::Middleware::RevisionMiddleware.new(app)
8
+ end
9
+
10
+ context "/system/revision" do
11
+ it "returns the current running revision" do
12
+ middleware = E20::Ops::Middleware::RevisionMiddleware.new(app, :revision => "rev")
13
+ status, headers, body = middleware.call({"PATH_INFO" => "/system/revision"})
14
+ body.should == "rev\n"
15
+ end
16
+ end
17
+
18
+ context "any other endpoint" do
19
+ it "delegates to the app" do
20
+ middleware = E20::Ops::Middleware::RevisionMiddleware.new(app)
21
+ status, headers, body = middleware.call({})
22
+ body.should == "OK!"
23
+ end
24
+
25
+ it "logs the running revision when initialized" do
26
+ log_io = StringIO.new
27
+ E20::Ops::Middleware::RevisionMiddleware.new(app, :revision => "rev", :logger => Logger.new(log_io))
28
+ log_io.string.should include("[E20::Ops::Middleware::RevisionMiddleware] Running: rev")
29
+ end
30
+
31
+ it "sets an X-Revision header" do
32
+ middleware = E20::Ops::Middleware::RevisionMiddleware.new(app, :revision => "the_revision", :logger => nil)
33
+ status, headers, body = middleware.call({})
34
+ headers["X-Revision"].should == "the_revision"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe E20::Ops::Middleware::TransactionIdMiddleware do
4
+ let(:app) { Proc.new { |env| [200, {}, "OK!"] } }
5
+ let(:uuid) { stub(:generate => "abc123") }
6
+ let(:logger) { Logger.new(StringIO.new) }
7
+
8
+ it "is initialized with an app" do
9
+ E20::Ops::Middleware::TransactionIdMiddleware.new(app)
10
+ end
11
+
12
+ it "delegates to the app" do
13
+ middleware = E20::Ops::Middleware::TransactionIdMiddleware.new(app, :logger => logger)
14
+ status, headers, body = middleware.call({})
15
+ body.should == "OK!"
16
+ end
17
+
18
+ it "sets an X-Transaction header" do
19
+ middleware = E20::Ops::Middleware::TransactionIdMiddleware.new(app, :uuid_generator => uuid, :logger => logger)
20
+ status, headers, body = middleware.call({})
21
+ headers["X-Transaction"].should == "abc123"
22
+ end
23
+
24
+ it "logs a line for each request" do
25
+ log_io = StringIO.new
26
+ middleware = E20::Ops::Middleware::TransactionIdMiddleware.new(app, :uuid_generator => uuid, :logger => Logger.new(log_io))
27
+ middleware.call({})
28
+ log_io.string.should include("[E20::Ops::Middleware::TransactionIdMiddleware] Transaction ID: abc123")
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe E20::Ops::Revision do
4
+ context "when a REVISION file is present" do
5
+ it "adds a X-Revision header with the REVISION" do
6
+ tmp_path = Pathname.new(Dir.tmpdir)
7
+ tmp_path.join("REVISION").open("w") { |f| f.write "hello\n" }
8
+ E20::Ops::Revision.new(tmp_path).to_s.should == "hello"
9
+ end
10
+ end
11
+
12
+ context "when a REVISION file is not present" do
13
+ it "adds a X-Revision header with the git rev-parse HEAD" do
14
+ revision = E20::Ops::Revision.new
15
+ revision.should_receive(:`).with("git rev-parse HEAD").and_return("abc123")
16
+ revision.to_s.should == "abc123"
17
+ end
18
+ end
19
+
20
+ context "when neither a REVISION file or a git revision are available" do
21
+ it "adds a X-Revision header of 'unknown'" do
22
+ revision = E20::Ops::Revision.new
23
+ revision.stub(:` => "")
24
+ revision.to_s.should == "unknown"
25
+ end
26
+ end
27
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,2 @@
1
+ require "spec"
2
+ require "e20/ops/middleware"
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: e20_ops_middleware
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease: false
6
+ segments:
7
+ - 2
8
+ - 0
9
+ - 1
10
+ version: 2.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Efficiency 2.0
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-29 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ type: :runtime
24
+ name: uuid
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 11
31
+ segments:
32
+ - 2
33
+ - 1
34
+ - 0
35
+ version: 2.1.0
36
+ requirement: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ prerelease: false
39
+ type: :development
40
+ name: rspec
41
+ version_requirements: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 27
47
+ segments:
48
+ - 1
49
+ - 3
50
+ - 0
51
+ version: 1.3.0
52
+ requirement: *id002
53
+ description: Adds middleware for debugging purposes
54
+ email: tech@efficiency20.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - README.md
61
+ files:
62
+ - .gitignore
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - History.txt
66
+ - README.md
67
+ - Rakefile
68
+ - VERSION
69
+ - ci.rb
70
+ - lib/e20/ops/hostname.rb
71
+ - lib/e20/ops/middleware.rb
72
+ - lib/e20/ops/middleware/hostname_middleware.rb
73
+ - lib/e20/ops/middleware/revision_middleware.rb
74
+ - lib/e20/ops/middleware/transaction_id_middleware.rb
75
+ - lib/e20/ops/revision.rb
76
+ - spec/ops/hostname_spec.rb
77
+ - spec/ops/middleware/hostname_middleware_spec.rb
78
+ - spec/ops/middleware/revision_middleware_spec.rb
79
+ - spec/ops/middleware/transaction_id_middleware_spec.rb
80
+ - spec/ops/revision_spec.rb
81
+ - spec/spec.opts
82
+ - spec/spec_helper.rb
83
+ has_rdoc: true
84
+ homepage: http://github.com/efficiency20/ops_middleware
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --charset=UTF-8
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Collection of useful middleware for exposing information about deployed Rack applications
117
+ test_files:
118
+ - spec/ops/hostname_spec.rb
119
+ - spec/ops/middleware/hostname_middleware_spec.rb
120
+ - spec/ops/middleware/revision_middleware_spec.rb
121
+ - spec/ops/middleware/transaction_id_middleware_spec.rb
122
+ - spec/ops/revision_spec.rb
123
+ - spec/spec_helper.rb