doctor-strange 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 782f2977af71550ceaaf9c022cb0ebe2a64c848a
4
+ data.tar.gz: 026347d2385308caf7fc6e94783c8cbcaa673622
5
+ SHA512:
6
+ metadata.gz: 38e0ca7bef140169251ad1ca321f8abdc286f6285e361582b10aa1158b551c6c1a170bd3ed7131d236335c0612a8dfb13a599b3b1e5e38dabeda867775fe4146
7
+ data.tar.gz: 4e263004f6940811be98d4d8cccf45121121d33b74cb933bff102cc3783592062c1a3bc5b54e77573e92078a563b2912c7f05257e2cad38c3195b37c3e8f31cf
@@ -0,0 +1,315 @@
1
+ # Doctor Strange
2
+
3
+ [![BuildStatus](https://circleci.com/gh/SiliconJungles/doctor-strange.svg?style=svg)](https://circleci.com/gh/SiliconJungles/doctor-strange)
4
+
5
+ Monitoring various services (db, cache, sidekiq/resque, email, redis, payment, etc).
6
+
7
+ ## Examples
8
+
9
+ ### HTML Status Page
10
+
11
+ ![alt example](/docs/screenshots/status-page.png "SiliconJungles Status Page")
12
+
13
+ ### JSON Response
14
+
15
+ ```bash
16
+ >> curl -s http://localhost:3000/check.json | json_pp
17
+ ```
18
+
19
+ ```json
20
+ {
21
+ "results": [{
22
+ "name": "Database",
23
+ "message": "",
24
+ "status": "OK"
25
+ }, {
26
+ "name": "Redis",
27
+ "message": "",
28
+ "status": "OK"
29
+ }, {
30
+ "name": "Sidekiq",
31
+ "message": "",
32
+ "status": "OK"
33
+ }, {
34
+ "name": "Email",
35
+ "message": "",
36
+ "status": "OK"
37
+ }, {
38
+ "name": "Payment",
39
+ "message": "",
40
+ "status": "OK"
41
+ }],
42
+ "status": "ok",
43
+ "timestamp": "2018-03-12 16:36:55 +0800"
44
+ }
45
+ ```
46
+
47
+ ### XML Response
48
+
49
+ ```bash
50
+ >> curl -s http://localhost:3000/check.xml
51
+ ```
52
+
53
+ ```xml
54
+ <?xml version="1.0" encoding="UTF-8"?>
55
+ <hash>
56
+ <results type="array">
57
+ <result>
58
+ <name>Database</name>
59
+ <message />
60
+ <status>OK</status>
61
+ </result>
62
+ <result>
63
+ <name>Redis</name>
64
+ <message />
65
+ <status>OK</status>
66
+ </result>
67
+ <result>
68
+ <name>Sidekiq</name>
69
+ <message />
70
+ <status>OK</status>
71
+ </result>
72
+ <result>
73
+ <name>Email</name>
74
+ <message />
75
+ <status>OK</status>
76
+ </result>
77
+ <result>
78
+ <name>Payment</name>
79
+ <message />
80
+ <status>OK</status>
81
+ </result>
82
+ </results>
83
+ <status type="symbol">ok</status>
84
+ <timestamp>2018-03-12 16:38:31 +0800</timestamp>
85
+ </hash>
86
+ ```
87
+
88
+ ## Setup
89
+
90
+ If you are using bundler add doctor-strange to your Gemfile:
91
+
92
+ ```ruby
93
+ gem 'doctor-strange'
94
+ ```
95
+
96
+ Then run:
97
+
98
+ ```bash
99
+ $ bundle install
100
+ ```
101
+
102
+ Otherwise install the gem:
103
+
104
+ ```bash
105
+ $ gem install doctor-strange
106
+ ```
107
+
108
+ ## Usage
109
+ You can mount this inside your app routes by adding this to config/routes.rb:
110
+
111
+ ```ruby
112
+ mount DoctorStrange::Engine, at: '/'
113
+ ```
114
+
115
+ ## Supported Service Providers
116
+ The following services are currently supported:
117
+
118
+ * DB
119
+ * Cache
120
+ * Redis
121
+ * Sidekiq
122
+ * Resque
123
+ * Stripe
124
+ * MailGun
125
+
126
+ ## Configuration
127
+
128
+ ### Adding AppName
129
+
130
+ The app name is `SiliconJungles` by default. You can change to your app name.
131
+
132
+ ```ruby
133
+ DoctorStrange.configure do |config|
134
+ config.app_name = "YOUR_APP_NAME"
135
+ end
136
+ ```
137
+
138
+ ### Adding Providers
139
+ By default, only the database check is enabled. You can add more service providers by explicitly enabling them via an initializer:
140
+
141
+ ```ruby
142
+ DoctorStrange.configure do |config|
143
+ config.cache
144
+ config.redis
145
+ config.sidekiq
146
+ # etc
147
+ end
148
+ ```
149
+
150
+ We believe that having the database check enabled by default is very important, but if you still want to disable it
151
+ (e.g., if you use a database that isn't covered by the check) - you can do that by calling the `no_database` method:
152
+
153
+ ```ruby
154
+ DoctorStrange.configure do |config|
155
+ config.no_database
156
+ end
157
+ ```
158
+
159
+ ### Provider Configuration
160
+
161
+ Some of the providers can also accept additional configuration:
162
+
163
+ ```ruby
164
+ # Sidekiq
165
+ DoctorStrange.configure do |config|
166
+ config.sidekiq.configure do |sidekiq_config|
167
+ sidekiq_config.latency = 3.hours
168
+ sidekiq_config.queue_size = 50
169
+ end
170
+ end
171
+ ```
172
+
173
+ ```ruby
174
+ # Redis
175
+ DoctorStrange.configure do |config|
176
+ config.redis.configure do |redis_config|
177
+ redis_config.connection = Redis.current # use your custom redis connection
178
+ redis_config.url = 'redis://user:pass@example.redis.com:90210/' # or URL
179
+ redis_config.max_used_memory = 200 # Megabytes
180
+ end
181
+ end
182
+ ```
183
+
184
+ ```ruby
185
+ # Email - MailGun by default
186
+ DoctorStrange.configure do |config|
187
+ config.email.configure do |email_config|
188
+ email_config.api_key = "abcd"
189
+ email_config.domain = "foo.bar"
190
+ end
191
+ end
192
+ ```
193
+
194
+ ```ruby
195
+ # Payment - Stripe by default
196
+ DoctorStrange.configure do |config|
197
+ config.payment.configure do |payment_config|
198
+ payment_config.api_key = "sk_live_xxxxx"
199
+ end
200
+ end
201
+ ```
202
+
203
+ The currently supported settings are:
204
+
205
+ #### Sidekiq
206
+
207
+ * `latency`: the latency (in seconds) of a queue (now - when the oldest job was enqueued) which is considered unhealthy (the default is 30 seconds, but larger processing queue should have a larger latency value).
208
+ * `queue_size`: the size (maximim) of a queue which is considered unhealthy (the default is 100).
209
+
210
+ #### Redis
211
+
212
+ * `url`: the url used to connect to your Redis instance - note, this is an optional configuration and will use the default connection if not specified
213
+ * `connection`: Use custom redis connection (e.g., `Redis.current`).
214
+ * `max_used_memory`: Set maximum expected memory usage of Redis in megabytes. Prevent memory leaks and keys overstore.
215
+
216
+ #### Email
217
+
218
+ - Default transaction email service for now is `MailGun`.
219
+
220
+ * `api_key`: the mail service api_key - this is a required value and will return unhealthy if not specified
221
+ * `domain`: the mail service domain value - this is a required value and will return unhealthy if not specified.
222
+
223
+ #### Payment
224
+
225
+ - Default payment service for now is `Stripe`.
226
+
227
+ * `api_key`: The Payment service's api_key - returns unhealthy if not specified or invalid.
228
+
229
+ ### Adding a Custom Provider
230
+ It's also possible to add custom health check providers suited for your needs (of course, it's highly appreciated and encouraged if you'd contribute useful providers to the project).
231
+
232
+ In order to add a custom provider, you'd need to:
233
+
234
+ * Implement the `DoctorStrange::Providers::Base` class and its `check!` method (a check is considered as failed if it raises an exception):
235
+
236
+ ```ruby
237
+ class CustomProvider < DoctorStrange::Providers::Base
238
+ def check!
239
+ raise 'Oh oh!'
240
+ end
241
+ end
242
+ ```
243
+ * Add its class to the configuration:
244
+
245
+ ```ruby
246
+ DoctorStrange.configure do |config|
247
+ config.add_custom_provider(CustomProvider)
248
+ end
249
+ ```
250
+
251
+ ### Adding a Custom Error Callback
252
+ If you need to perform any additional error handling (for example, for additional error reporting), you can configure a custom error callback:
253
+
254
+ ```ruby
255
+ DoctorStrange.configure do |config|
256
+ config.error_callback = proc do |e|
257
+ logger.error "Health check failed with: #{e.message}"
258
+
259
+ Raven.capture_exception(e)
260
+ end
261
+ end
262
+ ```
263
+
264
+ ### Adding Authentication Credentials
265
+ By default, the `/check` endpoint is not authenticated and is available to any user. You can authenticate using HTTP Basic Auth by providing authentication credentials:
266
+
267
+ ```ruby
268
+ DoctorStrange.configure do |config|
269
+ config.basic_auth_credentials = {
270
+ username: 'SECRET_NAME',
271
+ password: 'Shhhhh!!!'
272
+ }
273
+ end
274
+ ```
275
+
276
+ ### Adding Environment Variables
277
+ By default, environment variables is `nil`, so if you'd want to include additional parameters in the results JSON, all you need is to provide a `Hash` with your custom environment variables:
278
+
279
+ ```ruby
280
+ DoctorStrange.configure do |config|
281
+ config.environment_variables = {
282
+ build_number: 'BUILD_NUMBER',
283
+ git_sha: 'GIT_SHA'
284
+ }
285
+ end
286
+ ```
287
+
288
+ ## TODO
289
+
290
+ - [ ] Support another transaction Email services such as: SES, Mandrill by MailChimp, SendGird, AliCloud's DirectMail, etc.
291
+
292
+ - [ ] Support another payment services such as: Adyen, Braintree, etc.
293
+
294
+ ## License
295
+
296
+ The MIT License (MIT)
297
+
298
+ Copyright (c) 2018
299
+
300
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
301
+ this software and associated documentation files (the "Software"), to deal in
302
+ the Software without restriction, including without limitation the rights to
303
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
304
+ the Software, and to permit persons to whom the Software is furnished to do so,
305
+ subject to the following conditions:
306
+
307
+ The above copyright notice and this permission notice shall be included in all
308
+ copies or substantial portions of the Software.
309
+
310
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
311
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
312
+ FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
313
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
314
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
315
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new('spec')
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ task default: :spec
10
+
11
+ RuboCop::RakeTask.new
@@ -0,0 +1,47 @@
1
+ module DoctorStrange
2
+ class HealthController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ if Rails.version.starts_with? '3'
6
+ before_filter :authenticate_with_basic_auth
7
+ else
8
+ before_action :authenticate_with_basic_auth
9
+ end
10
+
11
+ def check
12
+ @statuses = statuses
13
+ @app_name = DoctorStrange.configuration.app_name
14
+
15
+ respond_to do |format|
16
+ format.html
17
+ format.json do
18
+ render json: statuses.to_json
19
+ end
20
+ format.xml do
21
+ render xml: statuses.to_xml
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def statuses
29
+ res = DoctorStrange.check(request: request)
30
+ res.merge(env_vars)
31
+ end
32
+
33
+ def env_vars
34
+ v = DoctorStrange.configuration.environment_variables || {}
35
+ v.empty? ? {} : { environment_variables: v }
36
+ end
37
+
38
+ def authenticate_with_basic_auth
39
+ return true unless DoctorStrange.configuration.basic_auth_credentials
40
+
41
+ credentials = DoctorStrange.configuration.basic_auth_credentials
42
+ authenticate_or_request_with_http_basic do |name, password|
43
+ name == credentials[:username] && password == credentials[:password]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @app_name %>Status</title>
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <meta name="description" content="<%= @app_name %> Status">
8
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="container">
13
+ <h1 class="text-center"><%= @app_name %> Status Page</h1>
14
+ <table class="table table-hover table-bordered">
15
+ <thead>
16
+ <tr>
17
+ <th scope="col">#</th>
18
+ <th scope="col">Service</th>
19
+ <th scope="col">Status</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @statuses[:results].each_with_index do |status, index| %>
24
+ <% class_name = status[:status].downcase == 'error' ? 'text-danger' : 'text-success' %>
25
+ <tr>
26
+ <th scope="row"><%= index + 1 %></th>
27
+ <td> <%= status[:name] %></td>
28
+ <td class="<%= class_name %>">
29
+ <p><%= status[:status] %></p>
30
+ <%= status[:message] %>
31
+ </td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>
36
+
37
+
38
+ <!-- <div class="statuses">
39
+ <h1><%= @app_name %> Status Page</h1>
40
+ <% @statuses[:results].each do |status| %>
41
+ <div class="status status-<%= status[:status].downcase %>">
42
+ <div class="status-heading">
43
+ <span class="name"><%= status[:name] %></span>
44
+ <span class="state"><%= status[:status] %></span>
45
+ </div>
46
+ <div class="message"><%= status[:message] %></div>
47
+ </div>
48
+ <% end %>
49
+ </div> -->
50
+ <div class="text-center">
51
+ Powered by <a href="https://github.com/SiliconJungles/doctor-strange.git" target="_blank">Doctor Strange - SiliconJungles Team</a>
52
+ </div>
53
+ </div>
54
+ </body>
@@ -0,0 +1,5 @@
1
+ DoctorStrange::Engine.routes.draw do
2
+ controller :health do
3
+ get :check
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ # rubocop:disable Naming/FileName
2
+
3
+ require 'doctor_strange/version'
4
+ require 'doctor_strange/engine'
5
+ require 'doctor_strange/configuration'
6
+ require 'doctor_strange/monitor'
7
+
8
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,44 @@
1
+ module DoctorStrange
2
+ class Configuration
3
+ PROVIDERS = %i[cache database redis resque sidekiq email payment].freeze
4
+
5
+ DEFAULT_APP_NAME = "SiliconJungles".freeze
6
+
7
+ attr_accessor :error_callback, :basic_auth_credentials, :environment_variables, :app_name
8
+ attr_reader :providers
9
+
10
+ def initialize
11
+ database
12
+ @app_name = DEFAULT_APP_NAME
13
+ end
14
+
15
+ def no_database
16
+ @providers.delete(DoctorStrange::Providers::Database)
17
+ end
18
+
19
+ PROVIDERS.each do |provider_name|
20
+ define_method provider_name do |&_block|
21
+ require "doctor_strange/providers/#{provider_name}"
22
+
23
+ add_provider("DoctorStrange::Providers::#{provider_name.capitalize}".constantize)
24
+ end
25
+ end
26
+
27
+ def add_custom_provider(custom_provider_class)
28
+ unless custom_provider_class < DoctorStrange::Providers::Base
29
+ raise ArgumentError, 'custom provider class must implement '\
30
+ 'DoctorStrange::Providers::Base'
31
+ end
32
+
33
+ add_provider(custom_provider_class)
34
+ end
35
+
36
+ private
37
+
38
+ def add_provider(provider_class)
39
+ (@providers ||= Set.new) << provider_class
40
+
41
+ provider_class
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module DoctorStrange
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DoctorStrange
4
+ end
5
+ end
@@ -0,0 +1,51 @@
1
+ require 'doctor_strange/configuration'
2
+
3
+ module DoctorStrange
4
+ STATUSES = {
5
+ ok: 'OK',
6
+ error: 'ERROR'
7
+ }.freeze
8
+
9
+ extend self
10
+
11
+ attr_accessor :configuration
12
+
13
+ def configure
14
+ self.configuration ||= Configuration.new
15
+
16
+ yield configuration if block_given?
17
+ end
18
+
19
+ def check(request: nil)
20
+ results = configuration.providers.map { |provider| provider_result(provider, request) }
21
+
22
+ {
23
+ results: results,
24
+ status: results.all? { |res| res[:status] == STATUSES[:ok] } ? :ok : :service_unavailable,
25
+ timestamp: Time.now.to_s(:rfc2822)
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def provider_result(provider, request)
32
+ monitor = provider.new(request: request)
33
+ monitor.check!
34
+
35
+ {
36
+ name: provider.provider_name,
37
+ message: '',
38
+ status: STATUSES[:ok]
39
+ }
40
+ rescue StandardError => e
41
+ configuration.error_callback.call(e) if configuration.error_callback
42
+
43
+ {
44
+ name: provider.provider_name,
45
+ message: e.message,
46
+ status: STATUSES[:error]
47
+ }
48
+ end
49
+ end
50
+
51
+ DoctorStrange.configure
@@ -0,0 +1,39 @@
1
+ module DoctorStrange
2
+ module Providers
3
+ class Base
4
+ attr_reader :request
5
+ attr_accessor :configuration
6
+
7
+ def self.provider_name
8
+ @name ||= name.demodulize
9
+ end
10
+
11
+ def self.configure
12
+ return unless configurable?
13
+
14
+ @global_configuration = configuration_class.new
15
+
16
+ yield @global_configuration if block_given?
17
+ end
18
+
19
+ def initialize(request: nil)
20
+ @request = request
21
+
22
+ return unless self.class.configurable?
23
+ self.configuration = self.class.instance_variable_get('@global_configuration')
24
+ end
25
+
26
+ # @abstract
27
+ def check!
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def self.configurable?
32
+ configuration_class
33
+ end
34
+
35
+ # @abstract
36
+ def self.configuration_class; end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ require 'doctor_strange/providers/base'
2
+
3
+ module DoctorStrange
4
+ module Providers
5
+ class CacheException < StandardError; end
6
+
7
+ class Cache < Base
8
+ def check!
9
+ time = Time.now.to_s
10
+
11
+ Rails.cache.write(key, time)
12
+ fetched = Rails.cache.read(key)
13
+
14
+ raise "different values (now: #{time}, fetched: #{fetched})" if fetched != time
15
+ rescue StandardError => e
16
+ raise CacheException, e.message
17
+ end
18
+
19
+ private
20
+
21
+ def key
22
+ @key ||= ['health', request.try(:remote_ip)].join(':')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ require 'doctor_strange/providers/base'
2
+
3
+ module DoctorStrange
4
+ module Providers
5
+ class DatabaseException < StandardError; end
6
+
7
+ class Database < Base
8
+ def check!
9
+ # Check connection to the DB:
10
+ ActiveRecord::Base.establish_connection # Establishes connection
11
+ ActiveRecord::Base.connection # Calls connection object
12
+ raise DatabaseException.new("Your database is not connected") unless ActiveRecord::Base.connected?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ require 'doctor_strange/providers/base'
2
+ require 'mailgun'
3
+
4
+ module DoctorStrange
5
+ module Providers
6
+ class EmailException < StandardError; end
7
+
8
+ class Email < Base
9
+ class Configuration
10
+ DEFAULT_SERVICE_NAME = "MailGun".freeze
11
+
12
+ attr_accessor :api_key, :domain, :service_name
13
+
14
+ def initialize
15
+ @service_name = DEFAULT_SERVICE_NAME
16
+ end
17
+ end
18
+
19
+ class << self
20
+ private
21
+
22
+ def configuration_class
23
+ ::DoctorStrange::Providers::Email::Configuration
24
+ end
25
+ end
26
+
27
+ def check!
28
+ check_required_values!
29
+ check_communication!
30
+ rescue StandardError => e
31
+ raise EmailException, e.message
32
+ end
33
+
34
+ private
35
+
36
+ def check_required_values!
37
+ raise "The api_key and domain are required" if configuration.api_key.empty? || configuration.domain.empty?
38
+ end
39
+
40
+ def check_communication!
41
+ mg_client = Mailgun::Client.new(configuration.api_key)
42
+ domainer = Mailgun::Domains.new(mg_client)
43
+ domainer.list.pluck("name").include? configuration.domain
44
+ rescue Mailgun::CommunicationError
45
+ raise "Cannot communicate to Mailgun"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,48 @@
1
+ require 'doctor_strange/providers/base'
2
+ require 'stripe'
3
+
4
+ module DoctorStrange
5
+ module Providers
6
+ class PaymentException < StandardError; end
7
+
8
+ class Payment < Base
9
+ class Configuration
10
+ DEFAULT_SERVICE_NAME = "Stripe".freeze
11
+
12
+ attr_accessor :api_key, :service_name
13
+
14
+ def initialize
15
+ @service_name = DEFAULT_SERVICE_NAME
16
+ end
17
+ end
18
+
19
+ class << self
20
+ private
21
+
22
+ def configuration_class
23
+ ::DoctorStrange::Providers::Payment::Configuration
24
+ end
25
+ end
26
+
27
+ def check!
28
+ check_required_values!
29
+ check_communication!
30
+ rescue Exception => e
31
+ raise PaymentException.new(e.message)
32
+ end
33
+
34
+ private
35
+
36
+ def check_required_values!
37
+ raise "The api_key is required" if configuration.api_key.empty?
38
+ end
39
+
40
+ def check_communication!
41
+ Stripe.api_key = configuration.api_key
42
+ Stripe::Customer.list(limit: 1)
43
+ rescue Stripe::AuthenticationError
44
+ raise "Invalid API Key provided"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,77 @@
1
+ require 'doctor_strange/providers/base'
2
+
3
+ module DoctorStrange
4
+ module Providers
5
+ class RedisException < StandardError; end
6
+
7
+ class Redis < Base
8
+ class Configuration
9
+ DEFAULT_URL = nil
10
+
11
+ attr_accessor :url, :connection, :max_used_memory
12
+
13
+ def initialize
14
+ @url = DEFAULT_URL
15
+ end
16
+ end
17
+
18
+ class << self
19
+ private
20
+
21
+ def configuration_class
22
+ ::DoctorStrange::Providers::Redis::Configuration
23
+ end
24
+ end
25
+
26
+ def check!
27
+ check_values!
28
+ check_max_used_memory!
29
+ rescue Exception => e
30
+ raise RedisException.new(e.message)
31
+ ensure
32
+ redis.close
33
+ end
34
+
35
+ private
36
+
37
+ def check_values!
38
+ time = Time.now.to_s(:rfc2822)
39
+
40
+ redis.set(key, time)
41
+ fetched = redis.get(key)
42
+
43
+ raise "different values (now: #{time}, fetched: #{fetched})" if fetched != time
44
+ end
45
+
46
+ def check_max_used_memory!
47
+ return unless configuration.max_used_memory
48
+ return if used_memory_mb <= configuration.max_used_memory
49
+
50
+ raise "#{used_memory_mb}Mb memory using is higher than #{configuration.max_used_memory}Mb maximum expected"
51
+ end
52
+
53
+ def key
54
+ @key ||= ['health', request.try(:remote_ip)].join(':')
55
+ end
56
+
57
+ def redis
58
+ @redis =
59
+ if configuration.connection
60
+ configuration.connection
61
+ elsif configuration.url
62
+ ::Redis.new(url: configuration.url)
63
+ else
64
+ ::Redis.new
65
+ end
66
+ end
67
+
68
+ def bytes_to_megabytes(bytes)
69
+ (bytes.to_f / 1024 / 1024).round
70
+ end
71
+
72
+ def used_memory_mb
73
+ bytes_to_megabytes(redis.info['used_memory'])
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ require 'doctor_strange/providers/base'
2
+ require 'resque'
3
+
4
+ module DoctorStrange
5
+ module Providers
6
+ class ResqueException < StandardError; end
7
+
8
+ class Resque < Base
9
+ def check!
10
+ ::Resque.info
11
+ rescue Exception => e
12
+ raise ResqueException.new(e.message)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ require 'doctor_strange/providers/base'
2
+ require 'sidekiq/api'
3
+
4
+ module DoctorStrange
5
+ module Providers
6
+ class SidekiqException < StandardError; end
7
+
8
+ class Sidekiq < Base
9
+ class Configuration
10
+ DEFAULT_LATENCY_TIMEOUT = 30
11
+ DEFAULT_QUEUES_SIZE = 100
12
+
13
+ attr_accessor :latency, :queue_size
14
+
15
+ def initialize
16
+ @latency = DEFAULT_LATENCY_TIMEOUT
17
+ @queue_size = DEFAULT_QUEUES_SIZE
18
+ end
19
+ end
20
+
21
+ def check!
22
+ check_workers!
23
+ check_processes!
24
+ check_latency!
25
+ check_queue_size!
26
+ check_redis!
27
+ rescue Exception => e
28
+ raise SidekiqException, e.message
29
+ end
30
+
31
+ private
32
+
33
+ class << self
34
+ private
35
+
36
+ def configuration_class
37
+ ::DoctorStrange::Providers::Sidekiq::Configuration
38
+ end
39
+ end
40
+
41
+ def check_workers!
42
+ ::Sidekiq::Workers.new.size
43
+ end
44
+
45
+ def check_processes!
46
+ sidekiq_stats = ::Sidekiq::Stats.new
47
+ return unless sidekiq_stats.processes_size.zero?
48
+
49
+ raise 'Sidekiq alive processes number is 0!'
50
+ end
51
+
52
+ def check_latency!
53
+ latency = queue.latency
54
+
55
+ return unless latency > configuration.latency
56
+
57
+ raise "latency #{latency} is greater than #{configuration.latency}"
58
+ end
59
+
60
+ def check_queue_size!
61
+ size = queue.size
62
+
63
+ return unless size > configuration.queue_size
64
+
65
+ raise "queue size #{size} is greater than #{configuration.queue_size}"
66
+ end
67
+
68
+ def check_redis!
69
+ if ::Sidekiq.respond_to?(:redis_info)
70
+ ::Sidekiq.redis_info
71
+ else
72
+ ::Sidekiq.redis(&:info)
73
+ end
74
+ end
75
+
76
+ private def queue
77
+ @queue ||= ::Sidekiq::Queue.new
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoctorStrange
4
+ VERSION = '0.1.1'
5
+ end
metadata ADDED
@@ -0,0 +1,402 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: doctor-strange
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - SiliconJungles
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: appraisal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.2.0
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.2.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: capybara
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.18'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.18.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '2.18'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.18.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: capybara-screenshot
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.0'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.0.18
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.0'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.0.18
87
+ - !ruby/object:Gem::Dependency
88
+ name: coveralls
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '0.8'
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.8.21
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 0.8.21
107
+ - !ruby/object:Gem::Dependency
108
+ name: database_cleaner
109
+ requirement: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '1.6'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.6.2
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.6'
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 1.6.2
127
+ - !ruby/object:Gem::Dependency
128
+ name: mailgun-ruby
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1.1'
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 1.1.9
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '1.1'
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 1.1.9
147
+ - !ruby/object:Gem::Dependency
148
+ name: pry
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: 0.11.3
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: 0.11.3
161
+ - !ruby/object:Gem::Dependency
162
+ name: rake
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '0.8'
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: 0.8.7
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '0.8'
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 0.8.7
181
+ - !ruby/object:Gem::Dependency
182
+ name: rediska
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.5'
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: 0.5.0
191
+ type: :development
192
+ prerelease: false
193
+ version_requirements: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - "~>"
196
+ - !ruby/object:Gem::Version
197
+ version: '0.5'
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: 0.5.0
201
+ - !ruby/object:Gem::Dependency
202
+ name: resque
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - "~>"
206
+ - !ruby/object:Gem::Version
207
+ version: '1.27'
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: 1.27.4
211
+ type: :development
212
+ prerelease: false
213
+ version_requirements: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - "~>"
216
+ - !ruby/object:Gem::Version
217
+ version: '1.27'
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: 1.27.4
221
+ - !ruby/object:Gem::Dependency
222
+ name: rspec-rails
223
+ requirement: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - "~>"
226
+ - !ruby/object:Gem::Version
227
+ version: '3.7'
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: 3.7.2
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - "~>"
236
+ - !ruby/object:Gem::Version
237
+ version: '3.7'
238
+ - - ">="
239
+ - !ruby/object:Gem::Version
240
+ version: 3.7.2
241
+ - !ruby/object:Gem::Dependency
242
+ name: rspec_junit_formatter
243
+ requirement: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - "~>"
246
+ - !ruby/object:Gem::Version
247
+ version: 0.3.0
248
+ type: :development
249
+ prerelease: false
250
+ version_requirements: !ruby/object:Gem::Requirement
251
+ requirements:
252
+ - - "~>"
253
+ - !ruby/object:Gem::Version
254
+ version: 0.3.0
255
+ - !ruby/object:Gem::Dependency
256
+ name: rubocop
257
+ requirement: !ruby/object:Gem::Requirement
258
+ requirements:
259
+ - - "~>"
260
+ - !ruby/object:Gem::Version
261
+ version: '0.5'
262
+ type: :development
263
+ prerelease: false
264
+ version_requirements: !ruby/object:Gem::Requirement
265
+ requirements:
266
+ - - "~>"
267
+ - !ruby/object:Gem::Version
268
+ version: '0.5'
269
+ - !ruby/object:Gem::Dependency
270
+ name: sidekiq
271
+ requirement: !ruby/object:Gem::Requirement
272
+ requirements:
273
+ - - "~>"
274
+ - !ruby/object:Gem::Version
275
+ version: '3.0'
276
+ type: :development
277
+ prerelease: false
278
+ version_requirements: !ruby/object:Gem::Requirement
279
+ requirements:
280
+ - - "~>"
281
+ - !ruby/object:Gem::Version
282
+ version: '3.0'
283
+ - !ruby/object:Gem::Dependency
284
+ name: spork
285
+ requirement: !ruby/object:Gem::Requirement
286
+ requirements:
287
+ - - "~>"
288
+ - !ruby/object:Gem::Version
289
+ version: 0.9.2
290
+ type: :development
291
+ prerelease: false
292
+ version_requirements: !ruby/object:Gem::Requirement
293
+ requirements:
294
+ - - "~>"
295
+ - !ruby/object:Gem::Version
296
+ version: 0.9.2
297
+ - !ruby/object:Gem::Dependency
298
+ name: sqlite3
299
+ requirement: !ruby/object:Gem::Requirement
300
+ requirements:
301
+ - - "~>"
302
+ - !ruby/object:Gem::Version
303
+ version: '1.3'
304
+ - - ">="
305
+ - !ruby/object:Gem::Version
306
+ version: 1.3.13
307
+ type: :development
308
+ prerelease: false
309
+ version_requirements: !ruby/object:Gem::Requirement
310
+ requirements:
311
+ - - "~>"
312
+ - !ruby/object:Gem::Version
313
+ version: '1.3'
314
+ - - ">="
315
+ - !ruby/object:Gem::Version
316
+ version: 1.3.13
317
+ - !ruby/object:Gem::Dependency
318
+ name: stripe
319
+ requirement: !ruby/object:Gem::Requirement
320
+ requirements:
321
+ - - "~>"
322
+ - !ruby/object:Gem::Version
323
+ version: '3.11'
324
+ - - ">="
325
+ - !ruby/object:Gem::Version
326
+ version: 3.11.0
327
+ type: :development
328
+ prerelease: false
329
+ version_requirements: !ruby/object:Gem::Requirement
330
+ requirements:
331
+ - - "~>"
332
+ - !ruby/object:Gem::Version
333
+ version: '3.11'
334
+ - - ">="
335
+ - !ruby/object:Gem::Version
336
+ version: 3.11.0
337
+ - !ruby/object:Gem::Dependency
338
+ name: timecop
339
+ requirement: !ruby/object:Gem::Requirement
340
+ requirements:
341
+ - - "~>"
342
+ - !ruby/object:Gem::Version
343
+ version: 0.9.1
344
+ type: :development
345
+ prerelease: false
346
+ version_requirements: !ruby/object:Gem::Requirement
347
+ requirements:
348
+ - - "~>"
349
+ - !ruby/object:Gem::Version
350
+ version: 0.9.1
351
+ description: A Rails plug-in, which checks various services (db, cache, sidekiq, redis,
352
+ stripe, mailgun, etc.).
353
+ email:
354
+ - developers@siliconjungles.com
355
+ executables: []
356
+ extensions: []
357
+ extra_rdoc_files: []
358
+ files:
359
+ - README.md
360
+ - Rakefile
361
+ - app/controllers/doctor_strange/health_controller.rb
362
+ - app/views/doctor_strange/health/check.html.erb
363
+ - config/routes.rb
364
+ - lib/doctor-strange.rb
365
+ - lib/doctor_strange/configuration.rb
366
+ - lib/doctor_strange/engine.rb
367
+ - lib/doctor_strange/monitor.rb
368
+ - lib/doctor_strange/providers/base.rb
369
+ - lib/doctor_strange/providers/cache.rb
370
+ - lib/doctor_strange/providers/database.rb
371
+ - lib/doctor_strange/providers/email.rb
372
+ - lib/doctor_strange/providers/payment.rb
373
+ - lib/doctor_strange/providers/redis.rb
374
+ - lib/doctor_strange/providers/resque.rb
375
+ - lib/doctor_strange/providers/sidekiq.rb
376
+ - lib/doctor_strange/version.rb
377
+ homepage: https://github.com/SiliconJungles/doctor-strange
378
+ licenses:
379
+ - MIT
380
+ metadata: {}
381
+ post_install_message:
382
+ rdoc_options: []
383
+ require_paths:
384
+ - lib
385
+ required_ruby_version: !ruby/object:Gem::Requirement
386
+ requirements:
387
+ - - ">="
388
+ - !ruby/object:Gem::Version
389
+ version: '0'
390
+ required_rubygems_version: !ruby/object:Gem::Requirement
391
+ requirements:
392
+ - - ">="
393
+ - !ruby/object:Gem::Version
394
+ version: '0'
395
+ requirements: []
396
+ rubyforge_project:
397
+ rubygems_version: 2.6.13
398
+ signing_key:
399
+ specification_version: 4
400
+ summary: A Rails plug-in, which checks various services (db, cache, sidekiq, redis,
401
+ stripe, mailgun, etc.)
402
+ test_files: []