• Stars
    star
    191
  • Rank 202,877 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 11 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Easy Monitoring

Easymon

Easymon helps you monitor your application's availability. It provides a simple way to test the availability of resources your application needs, like the application database, a memcached connection, or a redis instance. These test results can be used by a load balancer to determine the general health and viability of the node your application is running on.

It's packaged up as a rails engine for 3.1 and greater, and a plugin for 2.3 - 3.0.

History

This gem extracts and modularizes the logic we had in our monitoring controllers and were copying back and forth between applications.

Installation

Add to Gemfile and bundle!:

gem 'easymon'

Usage

To get started, you'll need to add an initializer for this to do anything. In config/initializers/easymon.rb:

Easymon::Repository.add("application-database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base))

This will register a check called application-database for use.

Next, we need to add the routes to your application. Depending on the rails version, this is done one of two ways:

Rails 2.3.x & 3.0

Add Easymon.routes(map) to your config/routes.rb. This will put the Easymon routes under /up. If you want Easymon mounted somewhere other than /up, use Easymon.routes(map, "/monitoring"). That would put the Easymon paths under /monitoring. For Rails 3.0, the default routes file does not provide map, so use Easymon.routes(self) instead.

Rails 3.1+

Rails 3.1+ gives us mountable engines, so use the standard syntax, adding mount Easymon::Engine => "/up" to your config/routes.rb.

Now, you can run your entire checklist by visiting /up, or wherever you have mounted the application. If you want to just test the single check, go to /up/application-database, and only the check named application-database will be run.

Critical Checks

If you have several services that are critical to your app, and others that are not, you can segregate those for health check purposes if you wish. Assuming your database and redis are critical, but memcached is not, again in config/initializers/easymon.rb:

Easymon::Repository.add(
  "application-database",
  Easymon::ActiveRecordCheck.new(ActiveRecord::Base),
  :critical
)
Easymon::Repository.add(
  "redis",
  Easymon::RedisCheck.new(
    YAML.load_file(
      Rails.root.join("config/redis.yml")
    )[Rails.env].symbolize_keys
  ),
  :critical
)
Easymon::Repository.add(
  "memcached",
  Easymon::MemcachedCheck.new(Rails.cache)
)

In addition to the main route /up, this will register four checks, individually available at:

  • /up/application-database
  • /up/redis
  • /up/memcached
  • /up/critical - Runs both the application-database and redis checks.

Security

You might not want to have this data available to everyone who hits your site, as it can expose both timing data and, depending on your check names, various bits of your infrastructure. You can tell Easymon what addresses, headers, or whatever defines an authorized request by providing a block to Easymon.authorize_with that will be called with the current request object:

Easymon.authorize_with = Proc.new { |request| request.remote_ip == '192.168.1.1'}
# Or
Easymon.authorize_with = Proc.new { |request|
  request.headers["X-Forwarded-For"].nil?
}

This will get run on each request, so keep it simple. (Actually, that's a good rule of thumb for any checks you write, too. Remember, these are all in your main app request pipeline!)

Checks

A check can be any ruby code that responds_to? a #check method that returns a two element array. The first element is the result of executing the check and should be true or false. The second element is the message describing what's going on. The array would look something like this: [true, "Up"] in the case of a successful check or [false, "Timeout"] in the case of a failed check.

Included Checks

  • ActiveRecord
  • Redis
  • Memcached
  • Semaphore
  • Traffic Enabled
  • Split ActiveRecord
  • Http

ActiveRecord

Easymon::ActiveRecordCheck is a basic check that uses ActiveRecord to check the availability of your main database. It's usually invoked as such:

Easymon::ActiveRecordCheck.new(ActiveRecord::Base)

Internally, it checks klass.connection.active? where klass is whatever class you passed to the check. Usually this will be ActiveRecord::Base, but feel free to go crazy.

Redis

Easymon::RedisCheck will check the availability of a Redis server, given an appropriate config hash. Typically, we'll read the config off disk, but as long as you get a valid config hash, this will work:

Easymon::RedisCheck.new(
  YAML.load_file(Rails.root.join("config/redis.yml"))[Rails.env].symbolize_keys
)

This is the most visually complex test to instantiate, but it's only because we're loading the config from disk and getting the config block that matches the Rails.env in one line. As long as you pass a hash that can be used by Redis.new, it doesn't care where the config comes from.

Memcached

Easymon::MemcachedCheck is a basic check that will write and then read a key from the cache. It expects a cache instance to check, so it could be as easy as:

Easymon::MemcachedCheck.new(Rails.cache)

Semaphore

Easymon::Semaphore checks for the presence of a file on disk relative to the Rails.root of the current application.

check = Easymon::SemaphoreCheck.new("config/redis.yml")

This is mainly a check that gets subclassed by the next check.

Traffic Enabled

Easymon::TrafficEnabledCheck is fairly specific, but when we want a server to accept traffic, we can place a file in the Rails.root, and the load balancers can use the result of this check to help decide whether or not to send traffic to the node.

Easymon::TrafficEnabledCheck.new("enable-traffic")

This is a subclass of the Semaphore check mentioned above.

Split ActiveRecord

Easymon::SplitActiveRecordCheck is the most complicated check, as it's not something you can use out of the gate. Here we pass a block so we get a fresh instance of ActiveRecord::Base or whatever other class we might be using to make a secondary database connection.

For example, given the following other class:

module Easymon
  class Base < ActiveRecord::Base
    def establish_connection(spec = nil)
      if spec
        super
      elsif config = Easymon.database_configuration
        super config
      end
    end
    def database_configuration
      env = "#{Rails.env}_replica"
      config = YAML.load_file(Rails.root.join('config/database.yml'))[env]
    end
  end
end

We would check both it and ActiveRecord::Base like so:

check = Easymon::SplitActiveRecordCheck.new {
  [ActiveRecord::Base.connection, Easymon::Base.connection]
}
Easymon::Repository.add("split-database", check)

Http

Easymon::HttpCheck will check the return status of a HEAD request to a URL. Great for checking service endpoint availability! The following will make a request to port 9200 on localhost, which is where you might have Elasticsearch running:

Easymon::HttpCheck.new("http://localhost:9200")

Typically, we'll read an elasticsearch config off disk, and use the URL like so:

config = YAML.load_file(Rails.root.join("config/elasticsearch.yml"))[Rails.env].symbolize_keys
Easymon::HttpCheck.new(config[:url])

Testing

To run the tests, you need MySQL server installed and running, and accepting connections on localhost:3306 for the root user with a blank password, as configured in database.yml.

Create the MySQL test databases by running:

bundle exec rake db:create

To run tests on PostgreSQL, you need the server installed and running, and accepting connections on localhost:5432 for the dummy user. You can create the dummy user with the following command in psql:

CREATE USER dummy WITH PASSWORD 'dummy';

Then run the tests with:

bundle exec rake test

How to contribute

Here's the most direct way to get your work merged into the project:

  1. Fork the project
  2. Clone down your fork
  3. Create a feature branch
  4. Add your feature + tests
  5. Document new features in the README
  6. Make sure everything still passes by running the tests
  7. If necessary, rebase your commits into logical chunks, without errors
  8. Push the branch up
  9. Send a pull request for your branch

If you're going to make a major change ask first to make sure it's in line with the project goals.

To Do

See the issues page. 😄

Authors

License

See LICENSE

More Repositories

1

trix

A rich text editor for everyday writing
JavaScript
17,847
star
2

kamal

Deploy web apps anywhere.
Ruby
9,584
star
3

handbook

Basecamp Employee Handbook
6,165
star
4

omakub

Opinionated Ubuntu Setup
Shell
4,152
star
5

pow

Zero-configuration Rack server for Mac OS X
CoffeeScript
3,423
star
6

policies

37signals policies, terms, and legal. Share them; reuse them; contribute to them.
1,863
star
7

local_time

Rails engine for cache-friendly, client-side local time
CoffeeScript
1,791
star
8

marginalia

Attach comments to ActiveRecord's SQL queries
Ruby
1,676
star
9

mail_view

Visual email testing
Ruby
1,341
star
10

xip-pdns

PowerDNS pipe backend adapter powering xip.io
Shell
1,159
star
11

geared_pagination

Paginate Active Record sets at variable speeds
Ruby
758
star
12

wysihat

A WYSIWYG JavaScript framework
JavaScript
681
star
13

bcx-api

API documentation and wrappers for Basecamp 2
672
star
14

name_of_person

Presenting names of people in full, familiar, abbreviated, and initialized forms (but without titulation etc)
Ruby
647
star
15

console1984

The Rails console you love, 1984 style
Ruby
609
star
16

google_sign_in

Sign in (or up) with Google for Rails applications
Ruby
494
star
17

bc3-api

API documentation for Basecamp 4
472
star
18

intermission

intermission helps you perform zero down time application maintenance
Lua
364
star
19

audits1984

Auditing tool for Rails console sessions
Ruby
336
star
20

snapback_cache

A client side page cache for jquery.
JavaScript
316
star
21

full_request_logger

Make full request logs accessible via web UI
Ruby
305
star
22

mysql_role_swap

(Nearly) Zero interruption mysql maintenance script.
Ruby
282
star
23

concerning

Bite-sized separation of concerns
Ruby
201
star
24

api

API integration and more for Basecamp products (Basecamp, Highrise, Campfire, Backpack)
192
star
25

trashed

Tell StatsD about request time, GC, objects and more. Latest Rails 4 and Ruby 2.1 support, and ancient Rails 2 and Ruby 1.8 support.
Ruby
189
star
26

highrise-api

Official API documentation for Highrise
130
star
27

fast_remote_cache

A faster version of Capistrano's remote_cache deployment strategy
Ruby
125
star
28

mass_encryption

Ruby
106
star
29

platform_agent

Parse user agent to deduce the platform
Ruby
103
star
30

cached_externals

Symlink to external dependencies, rather than bloating your repositories with them
Ruby
100
star
31

homographic_spoofing

Toolkit to both detect and sanitize homographic spoofing attacks in URLs and Email addresses
Ruby
98
star
32

campfire-api

Official API documentation for Campfire
97
star
33

basecamp-classic-api

Official API documentation for Basecamp Classic
87
star
34

lufo

Tracks the most recent options chosen on a `<select>` element and displays them at the top of the list
JavaScript
87
star
35

powprox

Pow .dev sites, meet SSL and HTTP/2
Shell
83
star
36

libmemcached_store

ActiveSupport::Cache wrapper for libmemcached
Ruby
81
star
37

action_profiler

Profile Rails requests on a live app
Ruby
75
star
38

bc3-integrations

Ruby
73
star
39

project_search

Rails plugin that adds a script/find command for searching your project
Ruby
71
star
40

activestorage-office-previewer

Active Storage previewer for Microsoft Office files based on LibreOffice
Ruby
67
star
41

turbo-8-morphing-demo

Ruby
58
star
42

dumpsterfire-2020

Code that runs the dumpster
HTML
47
star
43

cognition

Match text; run commands. Works great for building a chatbot!
Ruby
37
star
44

snapshot

A rails plugin that provides tasks for creating and restoring snapshots of development data.
Ruby
34
star
45

backpack-api

Official API documentation for Backpack
Ruby
20
star
46

ruby-dev

Old Rubies on new Macs
15
star
47

orc

Orc(hestrator) - A really bad pow.cx clone for linux
Shell
10
star
48

cleversafe

Ruby
7
star
49

accessibility

Guidelines and tools we use at 37signals to make sure our apps are accessible
7
star
50

memcached_bench

Ruby
6
star
51

duo-api

Ruby Gem for communicating with the Duo Api
Ruby
6
star
52

Xamarin.iOS.OnePasswordExtension

1Password bindings for Xamarin.iOS
C#
5
star
53

mail

Ruby
4
star
54

composed_of_ipaddr

Compact IPv4 attributes for Active Record. Presents an unsigned int (4 bytes) as an IPAddr.
Ruby
4
star
55

house-style

37signals house style
Ruby
3
star
56

deep_hash_transform

Re-key a nested Hash to all-Symbol or -String keys. Rails 4+ backport.
Ruby
3
star
57

assets-redirect

Ruby
2
star
58

github-issues

Github Issue query CLI
Go
2
star
59

homebrew-dev

Old software to build old stuff on new Macs
Ruby
1
star
60

nsone

A stupid simple API client for NS1
Ruby
1
star