Skip to content

Commit

Permalink
Merge pull request #32 from prometheus/grobie/middleware-rewrite
Browse files Browse the repository at this point in the history
Improve rack middlewares
  • Loading branch information
grobie authored Feb 25, 2017
2 parents 6606b59 + 5c38163 commit 9e28f0d
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 215 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ http_requests.increment
### Rack middleware

There are two [Rack][2] middlewares available, one to expose a metrics HTTP
endpoint to be scraped by a prometheus server ([Exporter][9]) and one to trace all HTTP
endpoint to be scraped by a Prometheus server ([Exporter][9]) and one to trace all HTTP
requests ([Collector][10]).

It's highly recommended to enable gzip compression for the metrics endpoint,
Expand All @@ -45,14 +45,14 @@ for example by including the `Rack::Deflater` middleware.
# config.ru

require 'rack'
require 'prometheus/client/rack/collector'
require 'prometheus/client/rack/exporter'
require 'prometheus/middleware/collector'
require 'prometheus/middleware/exporter'

use Rack::Deflater, if: ->(env, status, headers, body) { body.any? && body[0].length > 512 }
use Prometheus::Client::Rack::Collector
use Prometheus::Client::Rack::Exporter
use Rack::Deflater, if: ->(_, _, _, body) { body.any? && body[0].length > 512 }
use Prometheus::Middleware::Collector
use Prometheus::Middleware::Exporter

run ->(env) { [200, {'Content-Type' => 'text/html'}, ['OK']] }
run ->(_) { [200, {'Content-Type' => 'text/html'}, ['OK']] }
```

Start the server and have a look at the metrics endpoint:
Expand Down Expand Up @@ -179,5 +179,5 @@ rake
[6]: https://codeclimate.com/github/prometheus/client_ruby.png
[7]: https://coveralls.io/repos/prometheus/client_ruby/badge.png?branch=master
[8]: https://github.com/prometheus/pushgateway
[9]: lib/prometheus/client/rack/exporter.rb
[10]: lib/prometheus/client/rack/collector.rb
[9]: lib/prometheus/middleware/exporter.rb
[10]: lib/prometheus/middleware/collector.rb
14 changes: 8 additions & 6 deletions examples/rack/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Rack example

A simple Rack application which shows how to use prometheus' `Rack::Exporter`
and `Rack::Collector` rack middlwares.
A simple Rack application which shows how to use the included
`Prometheus::Middleware::Exporter` and `Prometheus::Middleware::Collector`
middlwares.

## Run the example

Expand Down Expand Up @@ -36,23 +37,24 @@ You can now open the [example app](http://localhost:5000/) and its [metrics
page](http://localhost:5000/metrics) to inspect the output. The running
Prometheus server can be used to [play around with the metrics][rate-query].

[rate-query]: http://localhost:9090/graph#%5B%7B%22range_input%22%3A%221h%22%2C%22expr%22%3A%22rate(http_request_duration_seconds_count%5B1m%5D)%22%2C%22tab%22%3A0%7D%5D
[rate-query]: http://localhost:9090/graph#%5B%7B%22range_input%22%3A%221h%22%2C%22expr%22%3A%22rate(http_server_requests_total%5B1m%5D)%22%2C%22tab%22%3A0%7D%5D

## Collector

The example shown in [`config.ru`](config.ru) is a trivial rack application
using the default collector and exporter middlewares.

In order to use a custom label builder in the collector, change the line to
In order to use custom label builders in the collector, change the line to
something like this:

```ruby
use Prometheus::Client::Rack::Collector do |env|
use Prometheus::Middleware::Collector, counter_label_builder: ->(env, code) {
{
code: code,
method: env['REQUEST_METHOD'].downcase,
host: env['HTTP_HOST'].to_s,
path: env['PATH_INFO'].to_s,
http_version: env['HTTP_VERSION'].to_s,
}
end
}
```
23 changes: 18 additions & 5 deletions examples/rack/config.ru
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
require 'rack'
require 'prometheus/client/rack/collector'
require 'prometheus/client/rack/exporter'
require 'prometheus/middleware/collector'
require 'prometheus/middleware/exporter'

use Rack::Deflater, if: ->(_, _, _, body) { body.any? && body[0].length > 512 }
use Prometheus::Client::Rack::Collector
use Prometheus::Client::Rack::Exporter
use Prometheus::Middleware::Collector
use Prometheus::Middleware::Exporter

run ->(_) { [200, { 'Content-Type' => 'text/html' }, ['OK']] }
srand

app = lambda do |_|
case rand
when 0..0.8
[200, { 'Content-Type' => 'text/html' }, ['OK']]
when 0.8..0.95
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
else
raise NoMethodError, 'It is a bug!'
end
end

run app
82 changes: 0 additions & 82 deletions lib/prometheus/client/rack/collector.rb

This file was deleted.

91 changes: 0 additions & 91 deletions lib/prometheus/client/rack/exporter.rb

This file was deleted.

91 changes: 91 additions & 0 deletions lib/prometheus/middleware/collector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# encoding: UTF-8

require 'prometheus/client'

module Prometheus
module Middleware
# Collector is a Rack middleware that provides a sample implementation of a
# HTTP tracer.
#
# By default metrics are registered on the global registry. Set the
# `:registry` option to use a custom registry.
#
# The request counter metric is broken down by code, method and path by
# default. Set the `:counter_label_builder` option to use a custom label
# builder.
#
# The request duration metric is broken down by method and path by default.
# Set the `:duration_label_builder` option to use a custom label builder.
class Collector
attr_reader :app, :registry

def initialize(app, options = {})
@app = app
@registry = options[:registry] || Client.registry
@counter_lb = options[:counter_label_builder] || COUNTER_LB
@duration_lb = options[:duration_label_builder] || DURATION_LB

init_request_metrics
init_exception_metrics
end

def call(env) # :nodoc:
trace(env) { @app.call(env) }
end

protected

COUNTER_LB = proc do |env, code|
{
code: code,
method: env['REQUEST_METHOD'].downcase,
path: env['PATH_INFO'].to_s,
}
end

DURATION_LB = proc do |env, _|
{
method: env['REQUEST_METHOD'].downcase,
path: env['PATH_INFO'].to_s,
}
end

def init_request_metrics
@requests = @registry.counter(
:http_server_requests_total,
'The total number of HTTP requests handled by the Rack application.',
)
@durations = @registry.histogram(
:http_server_request_duration_seconds,
'The HTTP response duration of the Rack application.',
)
end

def init_exception_metrics
@exceptions = @registry.counter(
:http_server_exceptions_total,
'The total number of exceptions raised by the Rack application.',
)
end

def trace(env)
start = Time.now
yield.tap do |response|
duration = [(Time.now - start).to_f, 0.0].max
record(env, response.first.to_s, duration)
end
rescue => exception
@exceptions.increment(exception: exception.class.name)
raise
end

def record(env, code, duration)
@requests.increment(@counter_lb.call(env, code))
@durations.observe(@duration_lb.call(env, code), duration)
rescue
# TODO: log unexpected exception during request recording
nil
end
end
end
end
Loading

0 comments on commit 9e28f0d

Please sign in to comment.