Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve rack middlewares #32

Merged
merged 2 commits into from Feb 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
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
@@ -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
@@ -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
@@ -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.',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's from rack, then the metric name should contain "rack"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rack is the de facto standard interface for ruby HTTP servers. It can be implemented by many different servers (unicorn, thin, puma, ...). I thought about including it, but concluded after some discussions that including it in the metric name is neither expected nor helpful.

)
@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