🐶 Max, your app's best friend!
Max Page sniffs metrics in your app to check if is everything right.
In other words, it's a Ruby on Rails engine to create "status pages" for your project.
Max is similiar to what GitHub, Heroku, Atlassian and others do to keep their users noticed about the operational status. However, Max it's much more about the apps's data than its infrastructure. If you would like something to keep a history of incidents, subscriptions, and integrations with external, try specific tools like StatusPage.
Max get together your app's metrics, in a single page.
For metrics, I mean some numbers and verifications that help us to understand if the app is running fine as a whole. They are not necessarily infrastructure checks.
Examples of metrics:
- Check health of internal and external services;
- Number of users registered last 24h;
- Number of clients that are using the key features;
- Size of internal queue processing services;
- Nodes we are consuming in the Kubernetes cluster.
Installation
Install the maxpage
gem in your Rails app by using bundle
:
$ bundle add maxpage
After that, add the configuration file config/initializers/maxpage.rb
to start creating your metrics, like the following code:
MaxPage.setup do
metric 'Users registered last 24h' do
User.where("created_at > ?", 24.hours.ago).count
end
end
Mount the route for the status page in config/routes.rb
:
mount MaxPage::Engine => "/status"
In that case, we are going to accces the page using the /status
path: http://localhost:3000/status.
You can use Max exclusively to monitor other apps and services. In that case consider to use only /
instead /status
to mount the MaxPage
engine, as your Rails application would not need other routes.
Usage
To set up MaxPage
all we need is to create an initializer file to define metrics.
Before do it, let's establish some concepts:
- A
metric
is anything you want to monitor; - A
metric
has aname
and ablock
of code; - The
block
's result will be presented in the page; - The
block
's result can be verified to produce an warning or success message;
Let's to an example!
MaxPage.setup do
metric 'Users registered last 24h' do
User.where("created_at > ?", 24.hours.ago).count
end
end
In the above code we have a metric named "User registered last 24h", with a block code that count in the database the users registed last 24h, using ActiveRecord
.
⚠️ Important: As our configuration is under config/initializers
folder we MUST restart the Rails server to see the changes. Soon we're going to move the setup code to the app
directory and eliminate the need of restarting. Work in progress.
The option verify
Let's say that usually I have 30 new users per day in my app.
I would like to know when this number is less than 20, what means something wrong is happening.
So, we are going to add the verify
option to check this metric status:
MaxPage.setup do
metric 'Users registered >= 20 ', verify: { min: 20 } do
User.where("created_at > ?", 24.hours.ago).count
end
end
Alternatively, we could use verify: true
:
MaxPage.setup do
metric 'Users registered last 24h is more than 20', verify: true do
User.where("created_at > ?", 24.hours.ago).count > 20
end
end
Observe that when we use verify: true
, it's expected the metric returns true
. The page shows
a check icon (✅) if this condition is satisfied, otherwise, it shows a warning icon (⚠️).
At mostly, when I am checking numbers, I prefer to use verify with min
or max
.
Warning and success messages
On top of the page, Max print a message accordling the verifications.
If all the metrics have their verify satisfied, the success message
is printed.
Otherwise, we see the warning message
.
Remember that the verify
is not a required option, therefore if it is not defined we consider the metric is always Okay to the overall verification.
We can change these messages. In the following code I translated them to Portuguese:
StatusPage.setup do
success_message 'Tudo certo!'
warning_message 'Ops, tem algo de errado.'
metric 'Usuários cadastrados nas últimas 24 horas', verify: { min: 20 } do
User.where("created_at > ?", 24.hours.ago).count
end
end
Groups of metrics
It's possible to create group of metrics just to organize them.
We can do it as the following code:
MaxPage.setup do
group 'Application data' do
metric 'User registered last 24h' do
# ...
end
# ...
end
group 'Internal services status' do
metric 'ElasticSearch is up' do
# ...
end
metric 'Redis is up' do
# ...
end
end
end
Authentications and Authorizations
Sometimes our page presents sensible data that could't be published to everyone.
Thinking of this, we can use the before_action
option to define authentication and authorization rules.
Example:
MaxPage.setup do
before_action do
authenticate_user!
end
# ...
end
The before_action
block will be evaluated in before_action
callback, using the controller scope.
This is why you can use methods like authenticate_<resource_name>!
from Devise and authorize
from Pundit.
Examples
Health status
Here we are checking the standard library Net::HTTP
the request a URI:
metric 'Health check using Net::HTTP', verify: true do
# Rescue errors returning false
result = Net::HTTP.get(URI('https://example.com/health/check')) rescue false
# Double bang to return true because result is String if the request succeeded.
!!result
end
Now, some examples using HTTParty to check the HTTP status.
require 'httparty'
metric 'Health check', verify: true do
HTTParty.get('https://example.com').success?
end
# Will warn once https://example.com/health/check will return the 404 status.
metric 'Health check status code', verify: 200 do
HTTParty.get('https://example.com/health/check').code
end
Database records
metric 'PostgreSQL records count', description: "Heroku's Hobby Dev plan limits to 10,000", verify: { max: 10_000 } do
ActiveRecord::Base.connection.execute(%{
select sum(c.reltuples) as rows
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r'
and n.nspname not in ('information_schema','pg_catalog');
}).first['rows']
end
Delayed::Job
group "Delayed Job" do
metric 'Failures', verify: { max: 0 } do
Delayed::Job.where.not(failed_at: nil).count
end
metric 'Size of the queue "mailer"' do
Delayed::Job.where(queue: 'mailer').count
end
end
Bugsnag errors
require 'httparty'
metric 'Open errors on Bugsnag', verify: { max: 0 } do
project_id = '<PROJECT-ID>'
auth_token = '<AUTH-TOKEN>'
response = HTTParty.get "https://api.bugsnag.com/projects/#{project_id}?auth_token=#{auth_token}"
response['open_error_count']
end
Contributing
Contributions are welcome! Feel free to open an issue and pull request on GitHub.
License
The gem is available as open source under the terms of the MIT License.