cronitor 1.1.5 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/publish.yml +29 -0
- data/.github/workflows/test.yml +54 -0
- data/.rubocop.yml +42 -0
- data/Gemfile +2 -0
- data/README.md +180 -62
- data/Rakefile +8 -0
- data/bin/console +1 -0
- data/cronitor.gemspec +18 -12
- data/lib/cronitor.rb +64 -87
- data/lib/cronitor/config.rb +33 -0
- data/lib/cronitor/error.rb +9 -1
- data/lib/cronitor/monitor.rb +193 -0
- data/lib/cronitor/version.rb +4 -2
- metadata +70 -37
- data/.travis.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d91bccffc3a53dfe487e4740c00aa83b47fe31c4347a1bdc93e017bbb3467751
|
4
|
+
data.tar.gz: 5730c8a4742bab773c1e3582fa4f00298890171e91ef96b83fda8e0a6ebd6470
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '054173538e57d465de2cc25786a42ccdb80c2be0532f0526634153de3fd7c46f428cdeaded4dd84e6169a9aa5717439bab0b1b4cc9672b56b93543ae58183ae3'
|
7
|
+
data.tar.gz: 311901851f90644a7d0c46eeed1b2bd821880816090799d1601008e29fa19a4cad2199905f317a9a08225296d579ba054bc87456dadf19fe427f3487c0ac42ce
|
@@ -0,0 +1,29 @@
|
|
1
|
+
---
|
2
|
+
name: Publish
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
tags:
|
7
|
+
- '*'
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
publish:
|
11
|
+
needs:
|
12
|
+
- lint
|
13
|
+
- test
|
14
|
+
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- name: Set up Ruby 2.7
|
20
|
+
uses: actions/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: 2.7.x
|
23
|
+
- name: Build and test with Rake
|
24
|
+
run: |
|
25
|
+
gem install bundler
|
26
|
+
bundle install --jobs 4 --retry 3
|
27
|
+
bundle exec rake release
|
28
|
+
with:
|
29
|
+
api_key: ${{secrets.RUBYGEMS_API_KEY}}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
---
|
2
|
+
name: Test
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
pull_request:
|
9
|
+
branches:
|
10
|
+
- master
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
lint:
|
14
|
+
strategy:
|
15
|
+
fail-fast: false
|
16
|
+
matrix:
|
17
|
+
ruby:
|
18
|
+
- '2.7'
|
19
|
+
- '3.0'
|
20
|
+
|
21
|
+
runs-on: ubuntu-latest
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
- name: Set up Ruby 2.7
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
bundler-cache: true
|
30
|
+
- name: Run Rubocop
|
31
|
+
run: bundle exec rake rubocop
|
32
|
+
|
33
|
+
spec:
|
34
|
+
strategy:
|
35
|
+
fail-fast: false
|
36
|
+
matrix:
|
37
|
+
ruby:
|
38
|
+
- '2.7'
|
39
|
+
- '3.0'
|
40
|
+
|
41
|
+
runs-on: ubuntu-latest
|
42
|
+
|
43
|
+
steps:
|
44
|
+
- uses: actions/checkout@v2
|
45
|
+
- name: Set up Ruby 2.7
|
46
|
+
uses: ruby/setup-ruby@v1
|
47
|
+
with:
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
49
|
+
bundler-cache: true
|
50
|
+
- name: Build and test with Rake
|
51
|
+
run: |
|
52
|
+
gem install bundler
|
53
|
+
bundle install --jobs 4 --retry 3
|
54
|
+
bundle exec rake
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
---
|
2
|
+
require:
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
|
6
|
+
inherit_mode:
|
7
|
+
merge:
|
8
|
+
- Exclude
|
9
|
+
|
10
|
+
AllCops:
|
11
|
+
NewCops: enable
|
12
|
+
TargetRubyVersion: 2.5
|
13
|
+
Exclude:
|
14
|
+
- 'spec/cronitor_spec.rb'
|
15
|
+
|
16
|
+
Metrics/AbcSize:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Metrics/ClassLength:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Metrics/BlockLength:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Metrics/CyclomaticComplexity:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Metrics/PerceivedComplexity:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Metrics/MethodLength:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Style/Documentation:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/RaiseArgs:
|
38
|
+
EnforcedStyle: compact
|
39
|
+
|
40
|
+
Style/EachWithObject:
|
41
|
+
Enabled: false
|
42
|
+
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,104 +1,222 @@
|
|
1
|
-
# Cronitor
|
1
|
+
# Cronitor Ruby Library
|
2
2
|
|
3
|
-
|
3
|
+
![Test](https://github.com/cronitorio/cronitor-ruby/workflows/Test/badge.svg)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/cronitor.svg)](https://badge.fury.io/rb/cronitor)
|
5
5
|
|
6
|
-
[Cronitor](https://cronitor.io/) is a service for heartbeat-style monitoring of just about anything that can send an HTTP request.
|
7
6
|
|
8
|
-
|
7
|
+
[Cronitor](https://cronitor.io/) provides dead simple monitoring for cron jobs, daemons, queue workers, websites, APIs, and anything else that can send or receive an HTTP request. The Cronitor Ruby library provides convenient access to the Cronitor API from applications written in Ruby. See our [API docs](https://cronitor.io/docs/api) for detailed references on configuring monitors and sending telemetry pings.
|
8
|
+
|
9
|
+
In this guide:
|
10
|
+
|
11
|
+
- [Installation](##Installation)
|
12
|
+
- [Monitoring Background Jobs](##monitoring-background-jobs)
|
13
|
+
- [Sending Telemetry Events](##sending-telemetry-events)
|
14
|
+
- [Configuring Monitors](##configuring-monitors)
|
15
|
+
- [Package Configuration & Env Vars](##package-configuration)
|
16
|
+
- [Command Line Usage](##command-line-usage)
|
17
|
+
- [Contributing](##contributing)
|
9
18
|
|
10
19
|
## Installation
|
11
20
|
|
12
|
-
|
21
|
+
```
|
22
|
+
gem install cronitor
|
23
|
+
```
|
24
|
+
|
25
|
+
## Monitoring Background Jobs
|
26
|
+
|
27
|
+
The `Cronitor#job` helper will send telemetry events before calling your block and after it exits. If your block raises an exception a `fail` event will be sent (and the exception re-raised).
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'cronitor'
|
31
|
+
Cronitor.api_key = 'api_key_123'
|
32
|
+
|
33
|
+
Cronitor.job 'warehouse-replenishmenth-report' do
|
34
|
+
ReplenishmentReport.new(Date.today).run()
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### Integrating with Sidekiq
|
39
|
+
Cronitor provides a [separate library](https://github.com/cronitorio/cronitor-sidekiq) built with this SDK to make Sidekiq integration even easier.
|
40
|
+
|
41
|
+
|
42
|
+
## Sending Telemetry Events
|
43
|
+
|
44
|
+
If you want finer control over when/how [telemetry pings](https://cronitor.io/docs/telemetry-api) are sent,
|
45
|
+
you can instantiate a monitor and call `#ping`.
|
13
46
|
|
14
47
|
```ruby
|
15
|
-
|
48
|
+
require 'cronitor'
|
49
|
+
Cronitor.api_key = 'api_key_123'
|
50
|
+
|
51
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
52
|
+
|
53
|
+
monitor.ping # a basic heartbeat event
|
54
|
+
|
55
|
+
# optional params can be passed as kwargs
|
56
|
+
# complete list - https://cronitor.io/docs/telemetry-api#parameters
|
57
|
+
|
58
|
+
monitor.ping(state: 'run', env: 'staging') # a job/process has started in a staging environment
|
59
|
+
|
60
|
+
# a job/process has completed - include metrics for cronitor to record
|
61
|
+
monitor.ping(state: 'complete', metrics: {count: 1000, error_count: 17})
|
16
62
|
```
|
17
63
|
|
18
|
-
|
64
|
+
## Configuring Monitors
|
65
|
+
|
66
|
+
You can configure all of your monitors using a single YAML file. This can be version controlled and synced to Cronitor as part of
|
67
|
+
a deployment or build process. For details on all of the attributes that can be set, see the [Monitor API](https://cronitor.io/docs/monitor-api) documentation.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
require 'cronitor'
|
71
|
+
Cronitor.api_key = 'api_key_123'
|
19
72
|
|
20
|
-
|
73
|
+
# read config file and set credentials (if included).
|
74
|
+
Cronitor.read_config('./cronitor.yaml')
|
21
75
|
|
22
|
-
|
76
|
+
# sync config file's monitors to Cronitor.
|
77
|
+
Cronitor.apply_config
|
23
78
|
|
24
|
-
|
79
|
+
# send config file's monitors to Cronitor to validate correctness.
|
80
|
+
# monitors will not be saved.
|
81
|
+
Cronitor.validate_config
|
82
|
+
```
|
25
83
|
|
26
|
-
|
84
|
+
The `cronitor.yaml` file includes three top level keys `jobs`, `checks`, `events`. You can configure monitors under each key by defining [monitors](https://cronitor.io/docs/monitor-api#attributes).
|
85
|
+
|
86
|
+
```yaml
|
87
|
+
jobs:
|
88
|
+
nightly-database-backup:
|
89
|
+
schedule: 0 0 * * *
|
90
|
+
notify:
|
91
|
+
- devops-alert-pagerduty
|
92
|
+
assertions:
|
93
|
+
- metric.duration < 5 minutes
|
94
|
+
|
95
|
+
send-welcome-email:
|
96
|
+
schedule: every 10 minutes
|
97
|
+
assertions:
|
98
|
+
- metric.count > 0
|
99
|
+
- metric.duration < 30 seconds
|
100
|
+
|
101
|
+
check:
|
102
|
+
cronitor-homepage:
|
103
|
+
request:
|
104
|
+
url: https://cronitor.io
|
105
|
+
regions:
|
106
|
+
- us-east-1
|
107
|
+
- eu-central-1
|
108
|
+
- ap-northeast-1
|
109
|
+
assertions:
|
110
|
+
- response.code = 200
|
111
|
+
- response.time < 2s
|
112
|
+
|
113
|
+
cronitor-telemetry-api:
|
114
|
+
request:
|
115
|
+
url: https://cronitor.link/ping
|
116
|
+
assertions:
|
117
|
+
- response.body contains ok
|
118
|
+
- response.time < .25s
|
119
|
+
|
120
|
+
heartbeats:
|
121
|
+
production-deploy:
|
122
|
+
notify:
|
123
|
+
alerts: ['deploys-slack']
|
124
|
+
events: true # send alert when the event occurs
|
27
125
|
|
28
|
-
|
126
|
+
```
|
29
127
|
|
30
|
-
|
128
|
+
You can also create and update monitors by calling `Monitor.put`.
|
31
129
|
|
32
130
|
```ruby
|
33
131
|
require 'cronitor'
|
34
132
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
133
|
+
monitors = Cronitor::Monitor.put([
|
134
|
+
{
|
135
|
+
type: 'job',
|
136
|
+
key: 'send-customer-invoices',
|
137
|
+
schedule: '0 0 * * *',
|
138
|
+
assertions: [
|
139
|
+
'metric.duration < 5 min'
|
140
|
+
],
|
141
|
+
notify: ['devops-alerts-slack']
|
43
142
|
},
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
}
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
143
|
+
{
|
144
|
+
type: 'check',
|
145
|
+
key: 'Cronitor Homepage',
|
146
|
+
request: {
|
147
|
+
url: 'https://cronitor.io'
|
148
|
+
},
|
149
|
+
schedule: 'every 45 seconds',
|
150
|
+
assertions: [
|
151
|
+
'response.code = 200',
|
152
|
+
'response.time < 600ms',
|
153
|
+
]
|
154
|
+
}
|
155
|
+
])
|
54
156
|
```
|
55
157
|
|
56
|
-
|
158
|
+
### Pause, Reset, Delete
|
57
159
|
|
58
160
|
```ruby
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
],
|
161
|
+
require 'cronitor'
|
162
|
+
|
163
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
164
|
+
|
165
|
+
monitor.pause(24) # pause alerting for 24 hours
|
166
|
+
monitor.unpause # alias for .pause(0)
|
167
|
+
monitor.ok # manually reset to a passing state alias for monitor.ping({state: ok})
|
168
|
+
monitor.delete # destroy the monitor
|
68
169
|
```
|
69
170
|
|
70
|
-
|
171
|
+
## Package Configuration
|
71
172
|
|
72
|
-
|
173
|
+
The package needs to be configured with your account's `API key`, which is available on the [account settings](https://cronitor.io/settings) page. You can also optionally specify an `api_version` and an `environment`. If not provided, your account default is used. These can also be supplied using the environment variables `CRONITOR_API_KEY`, `CRONITOR_API_VERSION`, `CRONITOR_ENVIRONMENT`.
|
73
174
|
|
74
175
|
```ruby
|
75
|
-
|
76
|
-
|
77
|
-
|
176
|
+
require 'cronitor'
|
177
|
+
|
178
|
+
# your api keys can found here - https://cronitor.io/settings
|
179
|
+
Cronitor.api_key = 'apiKey123'
|
180
|
+
Cronitor.api_version = '2020-10-01'
|
181
|
+
Cronitor.environment = 'cluster_1_prod'
|
78
182
|
```
|
79
183
|
|
80
|
-
|
184
|
+
## Contributing
|
81
185
|
|
82
|
-
|
186
|
+
Pull requests and features are happily considered! By participating in this project you agree to abide by the [Code of Conduct](http://contributor-covenant.org/version/2/0).
|
83
187
|
|
84
|
-
|
188
|
+
### To contribute
|
85
189
|
|
86
|
-
|
87
|
-
my_monitor = Cronitor.new code: 'abcd'
|
88
|
-
```
|
190
|
+
Fork, then clone the repo:
|
89
191
|
|
90
|
-
|
192
|
+
git clone git@github.com:your-username/cronitor-ruby.git
|
91
193
|
|
92
|
-
## Development
|
93
194
|
|
94
|
-
|
195
|
+
Set up your machine:
|
95
196
|
|
96
|
-
|
197
|
+
bin/setup
|
97
198
|
|
98
|
-
## Contributing
|
99
199
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
200
|
+
Make sure the tests pass:
|
201
|
+
|
202
|
+
rake spec
|
203
|
+
|
204
|
+
|
205
|
+
Make your change. You can experiment using:
|
206
|
+
|
207
|
+
bin/console
|
208
|
+
|
209
|
+
|
210
|
+
Add tests for your change. Make the tests pass:
|
211
|
+
|
212
|
+
rake spec
|
213
|
+
|
214
|
+
Push to your fork and [submit a pull request]( https://github.com/cronitorio/cronitor-ruby/compare/)
|
215
|
+
|
216
|
+
|
217
|
+
## Release a new version
|
218
|
+
|
219
|
+
The bump gem makes this easy:
|
220
|
+
|
221
|
+
1. `rake bump:(major|minor|patch|pre)`
|
222
|
+
2. `rake release`
|
data/Rakefile
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/gem_tasks'
|
2
4
|
require 'rspec/core/rake_task'
|
3
5
|
require 'bump/tasks'
|
6
|
+
require 'rubocop/rake_task'
|
4
7
|
|
5
8
|
RSpec::Core::RakeTask.new(:spec)
|
6
9
|
|
10
|
+
RuboCop::RakeTask.new do |task|
|
11
|
+
task.requires << 'rubocop-rake'
|
12
|
+
task.requires << 'rubocop-rspec'
|
13
|
+
end
|
14
|
+
|
7
15
|
task default: :spec
|
data/bin/console
CHANGED
data/cronitor.gemspec
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require 'cronitor/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
8
|
spec.name = 'cronitor'
|
7
9
|
spec.version = Cronitor::VERSION
|
8
|
-
spec.authors = ['Jeff Byrnes']
|
9
|
-
spec.email = ['thejeffbyrnes@gmail.com']
|
10
|
+
spec.authors = ['Jeff Byrnes', 'August Flanagan']
|
11
|
+
spec.email = ['thejeffbyrnes@gmail.com', 'august@cronitor.io']
|
10
12
|
|
11
13
|
spec.summary = 'An interface for the Cronitor API'
|
12
|
-
spec.homepage = 'https://github.com/
|
14
|
+
spec.homepage = 'https://github.com/cronitorio/cronitor-ruby'
|
15
|
+
|
16
|
+
spec.required_ruby_version = '>= 2.5'
|
13
17
|
|
14
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
15
19
|
f.match(%r{^(test|spec|features)/})
|
@@ -18,14 +22,16 @@ Gem::Specification.new do |spec|
|
|
18
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
23
|
spec.require_paths = ['lib']
|
20
24
|
|
21
|
-
spec.
|
22
|
-
spec.add_dependency 'hashie', '~> 3.4'
|
25
|
+
spec.add_runtime_dependency 'httparty'
|
23
26
|
|
24
|
-
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.3'
|
27
|
-
spec.add_development_dependency 'pry', '~> 0.10'
|
28
|
-
spec.add_development_dependency 'webmock', '~> 1.21'
|
29
|
-
spec.add_development_dependency 'sinatra', '~> 1.4'
|
30
27
|
spec.add_development_dependency 'bump', '~> 0.1'
|
28
|
+
spec.add_development_dependency 'bundler'
|
29
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
32
|
+
spec.add_development_dependency 'rubocop', '~> 1.8'
|
33
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.5.1'
|
34
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
|
35
|
+
spec.add_development_dependency 'sinatra', '~> 2.0'
|
36
|
+
spec.add_development_dependency 'webmock', '~> 3.1'
|
31
37
|
end
|
data/lib/cronitor.rb
CHANGED
@@ -1,110 +1,87 @@
|
|
1
|
-
|
2
|
-
require 'cronitor/error'
|
3
|
-
require 'net/http'
|
4
|
-
require 'unirest'
|
5
|
-
require 'hashie'
|
6
|
-
|
7
|
-
class Cronitor
|
8
|
-
attr_accessor :token, :opts, :code
|
9
|
-
API_URL = 'https://cronitor.io/v1'.freeze
|
10
|
-
PING_URL = 'https://cronitor.link'.freeze
|
1
|
+
# frozen_string_literal: true
|
11
2
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'httparty'
|
6
|
+
require 'socket'
|
7
|
+
require 'time'
|
8
|
+
require 'yaml'
|
16
9
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
require 'cronitor/config'
|
11
|
+
require 'cronitor/error'
|
12
|
+
require 'cronitor/version'
|
13
|
+
require 'cronitor/monitor'
|
14
|
+
|
15
|
+
module Cronitor
|
16
|
+
def self.read_config(path = nil, output: false)
|
17
|
+
Cronitor.config = path || Cronitor.config
|
18
|
+
unless Cronitor.config
|
19
|
+
raise ConfigurationError.new(
|
20
|
+
"Must include a path by setting Cronitor.config or passing a path to read_config e.g. \
|
21
|
+
Cronitor.read_config('./cronitor.yaml')"
|
22
22
|
)
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
human_readable @opts[:rules] if @opts.key? :rules
|
25
|
+
conf = YAML.safe_load(File.read(Cronitor.config))
|
26
|
+
conf.each do |k, _v|
|
27
|
+
raise ConfigurationError.new("Invalid configuration variable: #{k}") unless Cronitor::YAML_KEYS.include?(k)
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def create
|
35
|
-
response = Unirest.post(
|
36
|
-
"#{API_URL}/monitors",
|
37
|
-
headers: default_headers.merge('Content-Type' => 'application/json'),
|
38
|
-
auth: { user: token },
|
39
|
-
parameters: opts.to_json
|
40
|
-
)
|
41
|
-
|
42
|
-
@code = response.body['code'] if valid? response
|
43
|
-
end
|
44
|
-
|
45
|
-
def exists?(name)
|
46
|
-
response = Unirest.get(
|
47
|
-
"#{API_URL}/monitors/#{URI.escape(name).gsub('[', '%5B').gsub(']', '%5D')}",
|
48
|
-
headers: default_headers,
|
49
|
-
auth: { user: token }
|
50
|
-
)
|
51
|
-
return false unless response.code == 200
|
52
|
-
|
53
|
-
@code = response.body['code']
|
30
|
+
Cronitor.api_key = conf[:api_key] if conf[:api_key]
|
31
|
+
Cronitor.api_version = conf[:api_version] if conf[:api_version]
|
32
|
+
Cronitor.environment = conf[:environment] if conf[:environment]
|
54
33
|
|
55
|
-
|
56
|
-
end
|
34
|
+
return unless output
|
57
35
|
|
58
|
-
|
59
|
-
|
60
|
-
|
36
|
+
monitors = []
|
37
|
+
Cronitor::MONITOR_TYPES.each do |t|
|
38
|
+
plural_t = "#{t}s"
|
39
|
+
to_parse = conf[t] || conf[plural_t] || nil
|
40
|
+
next unless to_parse
|
61
41
|
|
62
|
-
|
63
|
-
|
64
|
-
|
42
|
+
unless to_parse.is_a?(Hash)
|
43
|
+
raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
|
44
|
+
end
|
65
45
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
"#{rule[:time_unit]}"
|
46
|
+
to_parse.each do |key, m|
|
47
|
+
m['key'] = key
|
48
|
+
m['type'] = t
|
49
|
+
monitors << m
|
71
50
|
end
|
72
51
|
end
|
52
|
+
conf['monitors'] = monitors
|
53
|
+
conf
|
73
54
|
end
|
74
55
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
raise Cronitor::Error, error_msg(response.body)
|
56
|
+
def self.apply_config(rollback: false)
|
57
|
+
conf = read_config(output: true)
|
58
|
+
monitors = Monitor.put(monitors: conf.fetch('monitors', []), rollback: rollback)
|
59
|
+
puts("#{monitors.length} monitors #{rollback ? 'validated' : 'synced to Cronitor'}.")
|
60
|
+
rescue ValidationError => e
|
61
|
+
Cronitor.logger.error(e)
|
82
62
|
end
|
83
63
|
|
84
|
-
def
|
85
|
-
|
86
|
-
if value.respond_to? 'each'
|
87
|
-
value.each do |error_msg|
|
88
|
-
msg << "#{opt}: #{error_msg}"
|
89
|
-
end
|
90
|
-
else
|
91
|
-
msg << "#{opt}: #{value}"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
msg.join ' '
|
64
|
+
def self.validate_config
|
65
|
+
apply_config(rollback: true)
|
96
66
|
end
|
97
67
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
68
|
+
def self.job(key, &block)
|
69
|
+
monitor = Monitor.new(key)
|
70
|
+
series = Time.now.to_f
|
71
|
+
monitor.ping(state: 'run', series: series)
|
72
|
+
|
73
|
+
begin
|
74
|
+
block.call
|
75
|
+
monitor.ping(state: 'complete', series: series)
|
76
|
+
rescue StandardError => e
|
77
|
+
monitor.ping(state: 'fail', message: e.message[[0, e.message.length - 1600].max..-1], series: series)
|
78
|
+
raise e
|
79
|
+
end
|
105
80
|
end
|
106
81
|
|
107
|
-
def
|
108
|
-
|
82
|
+
def self.monitor_api_url
|
83
|
+
'https://cronitor.io/api/monitors'
|
109
84
|
end
|
110
85
|
end
|
86
|
+
|
87
|
+
Cronitor.read_config(Cronitor.config) unless Cronitor.config.nil?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cronitor
|
4
|
+
TYPE_JOB = 'job'
|
5
|
+
TYPE_HEARTBEAT = 'heartbeat'
|
6
|
+
TYPE_CHECK = 'check'
|
7
|
+
MONITOR_TYPES = [TYPE_JOB, TYPE_HEARTBEAT, TYPE_CHECK].freeze
|
8
|
+
YAML_KEYS = %w[
|
9
|
+
api_key
|
10
|
+
api_version
|
11
|
+
environment
|
12
|
+
] + MONITOR_TYPES.map { |t| "#{t}s" }
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :api_key, :api_version, :environment, :logger, :config, :_headers
|
16
|
+
|
17
|
+
def configure(&block)
|
18
|
+
block.call(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self.api_key = ENV['CRONITOR_API_KEY']
|
23
|
+
self.api_version = ENV['CRONITOR_API_VERSION']
|
24
|
+
self.environment = ENV['CRONITOR_ENVIRONMENT']
|
25
|
+
self.config = ENV['CRONITOR_CONFIG']
|
26
|
+
self.logger = Logger.new($stdout)
|
27
|
+
logger.level = Logger::INFO
|
28
|
+
self._headers = {
|
29
|
+
'Content-Type': 'application/json',
|
30
|
+
'User-Agent': 'cronitor-ruby',
|
31
|
+
'Cronitor-Version': Cronitor.api_version
|
32
|
+
}
|
33
|
+
end
|
data/lib/cronitor/error.rb
CHANGED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cronitor
|
4
|
+
class Monitor
|
5
|
+
attr_reader :key, :api_key, :api_version, :env
|
6
|
+
|
7
|
+
PING_RETRY_THRESHOLD = 5
|
8
|
+
|
9
|
+
def self.put(opts = {})
|
10
|
+
rollback = opts[:rollback] || false
|
11
|
+
opts.delete(:rollback)
|
12
|
+
|
13
|
+
monitors = opts[:monitors] || [opts]
|
14
|
+
|
15
|
+
resp = HTTParty.put(
|
16
|
+
Cronitor.monitor_api_url,
|
17
|
+
basic_auth: {
|
18
|
+
username: Cronitor.api_key,
|
19
|
+
password: ''
|
20
|
+
},
|
21
|
+
body: {
|
22
|
+
monitors: monitors,
|
23
|
+
rollback: rollback
|
24
|
+
}.to_json,
|
25
|
+
headers: Cronitor._headers,
|
26
|
+
timeout: 10
|
27
|
+
)
|
28
|
+
|
29
|
+
case resp.code
|
30
|
+
when 200
|
31
|
+
out = []
|
32
|
+
data = JSON.parse(resp.body)
|
33
|
+
|
34
|
+
(data['monitors'] || []).each do |md|
|
35
|
+
m = Monitor.new(md['key'])
|
36
|
+
m.data = Cronitor.symbolize_keys(md)
|
37
|
+
out << m
|
38
|
+
end
|
39
|
+
out.length == 1 ? out[0] : out
|
40
|
+
when 400
|
41
|
+
raise ValidationError.new(resp.body)
|
42
|
+
else
|
43
|
+
raise Error.new("Error connecting to Cronitor: #{resp.code}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.delete(key)
|
48
|
+
resp = HTTParty.delete(
|
49
|
+
"#{Cronitor.monitor_api_url}/#{key}",
|
50
|
+
timeout: 10,
|
51
|
+
basic_auth: {
|
52
|
+
username: Cronitor.api_key,
|
53
|
+
password: ''
|
54
|
+
},
|
55
|
+
headers: Cronitor._headers
|
56
|
+
)
|
57
|
+
if resp.code != 204
|
58
|
+
Cronitor.logger.error("Error deleting monitor: #{key}")
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(key, api_key: nil, env: nil)
|
65
|
+
@key = key
|
66
|
+
@api_key = api_key || Cronitor.api_key
|
67
|
+
@env = env || Cronitor.environment
|
68
|
+
end
|
69
|
+
|
70
|
+
def data
|
71
|
+
return @data if defined?(@data)
|
72
|
+
|
73
|
+
@data = fetch
|
74
|
+
@data
|
75
|
+
end
|
76
|
+
|
77
|
+
def data=(data)
|
78
|
+
@data = Cronitor.symbolize_keys(data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def ping(params = {})
|
82
|
+
retry_count = params[:retry_count] || 0
|
83
|
+
if api_key.nil?
|
84
|
+
Cronitor.logger.error('No API key detected. Set Cronitor.api_key or initialize Monitor with an api_key:')
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
ping_url = ping_api_url
|
90
|
+
ping_url = fallback_ping_api_url if retry_count > (PING_RETRY_THRESHOLD / 2)
|
91
|
+
|
92
|
+
response = HTTParty.get(
|
93
|
+
ping_url,
|
94
|
+
query: clean_params(params),
|
95
|
+
timeout: 5,
|
96
|
+
headers: Cronitor._headers,
|
97
|
+
query_string_normalizer: lambda do |query|
|
98
|
+
query.compact!
|
99
|
+
metrics = query[:metric]
|
100
|
+
query.delete(:metric)
|
101
|
+
out = query.map { |k, v| "#{k}=#{v}" }
|
102
|
+
out += metrics.map { |m| "metric=#{m}" } unless metrics.nil?
|
103
|
+
out.join('&')
|
104
|
+
end
|
105
|
+
# query_string_normalizer for metrics. instead of metric[]=foo:1 we want metric=foo:1
|
106
|
+
)
|
107
|
+
|
108
|
+
if response.code != 200
|
109
|
+
Cronitor.logger.error("Cronitor Telemetry Error: #{response.code}")
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
true
|
113
|
+
rescue StandardError => e
|
114
|
+
# rescue instances of StandardError i.e. Timeout::Error, SocketError, etc
|
115
|
+
Cronitor.logger.error("Cronitor Telemetry Error: #{e}")
|
116
|
+
return false if retry_count >= Monitor::PING_RETRY_THRESHOLD
|
117
|
+
|
118
|
+
# apply a backoff before sending the next ping
|
119
|
+
sleep(retry_count * 1.5 * rand)
|
120
|
+
ping(params.merge(retry_count: params[:retry_count] + 1))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def ok
|
125
|
+
ping(state: 'ok')
|
126
|
+
end
|
127
|
+
|
128
|
+
def pause(hours = nil)
|
129
|
+
pause_url = "#{monitor_api_url}/pause"
|
130
|
+
pause_url += "/#{hours}" unless hours.nil?
|
131
|
+
|
132
|
+
resp = HTTParty.get(
|
133
|
+
pause_url,
|
134
|
+
timeout: 5,
|
135
|
+
headers: Cronitor._headers,
|
136
|
+
basic_auth: {
|
137
|
+
username: api_key,
|
138
|
+
password: ''
|
139
|
+
}
|
140
|
+
)
|
141
|
+
puts(resp.code)
|
142
|
+
resp.code == 200
|
143
|
+
end
|
144
|
+
|
145
|
+
def unpause
|
146
|
+
pause(0)
|
147
|
+
end
|
148
|
+
|
149
|
+
def ping_api_url
|
150
|
+
"https://cronitor.link/p/#{api_key}/#{key}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def fallback_ping_api_url
|
154
|
+
"https://cronitor.io/p/#{api_key}/#{key}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def monitor_api_url
|
158
|
+
"#{Cronitor.monitor_api_url}/#{key}"
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def fetch
|
164
|
+
unless api_key
|
165
|
+
Cronitor.logger.error(
|
166
|
+
'No API key detected. Set Cronitor.api_key or initialize Monitor with the api_key kwarg'
|
167
|
+
)
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
HTTParty.get(monitor_api_url, timeout: 10, headers: Cronitor._headers, format: :json)
|
172
|
+
end
|
173
|
+
|
174
|
+
def clean_params(params)
|
175
|
+
{
|
176
|
+
state: params.fetch(:state, nil),
|
177
|
+
message: params.fetch(:message, nil),
|
178
|
+
series: params.fetch(:series, nil),
|
179
|
+
host: params.fetch(:host, Socket.gethostname),
|
180
|
+
metric: params[:metrics] ? params[:metrics].map { |k, v| "#{k}:#{v}" } : nil,
|
181
|
+
stamp: Time.now.to_f,
|
182
|
+
env: params.fetch(:env, env)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.symbolize_keys(obj)
|
188
|
+
obj.inject({}) do |memo, (k, v)|
|
189
|
+
memo[k.to_sym] = v
|
190
|
+
memo
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/cronitor/version.rb
CHANGED
metadata
CHANGED
@@ -1,71 +1,86 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cronitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Byrnes
|
8
|
-
|
8
|
+
- August Flanagan
|
9
|
+
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-05-20 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
+
name: httparty
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- - "
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
+
version: '0'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- - "
|
25
|
+
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
+
version: '0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
29
|
+
name: bump
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
32
|
- - "~>"
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
type: :
|
34
|
+
version: '0.1'
|
35
|
+
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
39
|
- - "~>"
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '0.1'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pry
|
43
58
|
requirement: !ruby/object:Gem::Requirement
|
44
59
|
requirements:
|
45
60
|
- - "~>"
|
46
61
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
62
|
+
version: '0.10'
|
48
63
|
type: :development
|
49
64
|
prerelease: false
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
51
66
|
requirements:
|
52
67
|
- - "~>"
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
69
|
+
version: '0.10'
|
55
70
|
- !ruby/object:Gem::Dependency
|
56
71
|
name: rake
|
57
72
|
requirement: !ruby/object:Gem::Requirement
|
58
73
|
requirements:
|
59
|
-
- - "
|
74
|
+
- - ">="
|
60
75
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
76
|
+
version: '0'
|
62
77
|
type: :development
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- - "
|
81
|
+
- - ">="
|
67
82
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
83
|
+
version: '0'
|
69
84
|
- !ruby/object:Gem::Dependency
|
70
85
|
name: rspec
|
71
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,72 +96,89 @@ dependencies:
|
|
81
96
|
- !ruby/object:Gem::Version
|
82
97
|
version: '3.3'
|
83
98
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
99
|
+
name: rubocop
|
85
100
|
requirement: !ruby/object:Gem::Requirement
|
86
101
|
requirements:
|
87
102
|
- - "~>"
|
88
103
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
104
|
+
version: '1.8'
|
90
105
|
type: :development
|
91
106
|
prerelease: false
|
92
107
|
version_requirements: !ruby/object:Gem::Requirement
|
93
108
|
requirements:
|
94
109
|
- - "~>"
|
95
110
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
111
|
+
version: '1.8'
|
97
112
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
113
|
+
name: rubocop-rake
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 0.5.1
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.5.1
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rubocop-rspec
|
99
128
|
requirement: !ruby/object:Gem::Requirement
|
100
129
|
requirements:
|
101
130
|
- - "~>"
|
102
131
|
- !ruby/object:Gem::Version
|
103
|
-
version: '1
|
132
|
+
version: '2.1'
|
104
133
|
type: :development
|
105
134
|
prerelease: false
|
106
135
|
version_requirements: !ruby/object:Gem::Requirement
|
107
136
|
requirements:
|
108
137
|
- - "~>"
|
109
138
|
- !ruby/object:Gem::Version
|
110
|
-
version: '1
|
139
|
+
version: '2.1'
|
111
140
|
- !ruby/object:Gem::Dependency
|
112
141
|
name: sinatra
|
113
142
|
requirement: !ruby/object:Gem::Requirement
|
114
143
|
requirements:
|
115
144
|
- - "~>"
|
116
145
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
146
|
+
version: '2.0'
|
118
147
|
type: :development
|
119
148
|
prerelease: false
|
120
149
|
version_requirements: !ruby/object:Gem::Requirement
|
121
150
|
requirements:
|
122
151
|
- - "~>"
|
123
152
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
153
|
+
version: '2.0'
|
125
154
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
155
|
+
name: webmock
|
127
156
|
requirement: !ruby/object:Gem::Requirement
|
128
157
|
requirements:
|
129
158
|
- - "~>"
|
130
159
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
160
|
+
version: '3.1'
|
132
161
|
type: :development
|
133
162
|
prerelease: false
|
134
163
|
version_requirements: !ruby/object:Gem::Requirement
|
135
164
|
requirements:
|
136
165
|
- - "~>"
|
137
166
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
139
|
-
description:
|
167
|
+
version: '3.1'
|
168
|
+
description:
|
140
169
|
email:
|
141
170
|
- thejeffbyrnes@gmail.com
|
171
|
+
- august@cronitor.io
|
142
172
|
executables: []
|
143
173
|
extensions: []
|
144
174
|
extra_rdoc_files: []
|
145
175
|
files:
|
146
176
|
- ".editorconfig"
|
177
|
+
- ".github/workflows/publish.yml"
|
178
|
+
- ".github/workflows/test.yml"
|
147
179
|
- ".gitignore"
|
148
180
|
- ".rspec"
|
149
|
-
- ".
|
181
|
+
- ".rubocop.yml"
|
150
182
|
- Gemfile
|
151
183
|
- LICENSE.txt
|
152
184
|
- README.md
|
@@ -155,12 +187,14 @@ files:
|
|
155
187
|
- bin/setup
|
156
188
|
- cronitor.gemspec
|
157
189
|
- lib/cronitor.rb
|
190
|
+
- lib/cronitor/config.rb
|
158
191
|
- lib/cronitor/error.rb
|
192
|
+
- lib/cronitor/monitor.rb
|
159
193
|
- lib/cronitor/version.rb
|
160
|
-
homepage: https://github.com/
|
194
|
+
homepage: https://github.com/cronitorio/cronitor-ruby
|
161
195
|
licenses: []
|
162
196
|
metadata: {}
|
163
|
-
post_install_message:
|
197
|
+
post_install_message:
|
164
198
|
rdoc_options: []
|
165
199
|
require_paths:
|
166
200
|
- lib
|
@@ -168,16 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
202
|
requirements:
|
169
203
|
- - ">="
|
170
204
|
- !ruby/object:Gem::Version
|
171
|
-
version: '
|
205
|
+
version: '2.5'
|
172
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
207
|
requirements:
|
174
208
|
- - ">="
|
175
209
|
- !ruby/object:Gem::Version
|
176
210
|
version: '0'
|
177
211
|
requirements: []
|
178
|
-
|
179
|
-
|
180
|
-
signing_key:
|
212
|
+
rubygems_version: 3.1.4
|
213
|
+
signing_key:
|
181
214
|
specification_version: 4
|
182
215
|
summary: An interface for the Cronitor API
|
183
216
|
test_files: []
|
data/.travis.yml
DELETED