cronitor 3.1.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/publish.yml +29 -0
- data/.github/workflows/test.yml +54 -0
- data/.rubocop.yml +26 -0
- data/README.md +171 -76
- data/Rakefile +6 -0
- data/cronitor.gemspec +10 -3
- data/lib/cronitor.rb +63 -114
- data/lib/cronitor/config.rb +33 -0
- data/lib/cronitor/error.rb +7 -1
- data/lib/cronitor/monitor.rb +193 -0
- data/lib/cronitor/version.rb +2 -2
- metadata +66 -5
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d5c6dc2547c52ed2ccee489b2ea5428c6badd902f996ea076cd196e6c4bb1c3
|
4
|
+
data.tar.gz: 57b37c91645625096fbe19004d181a6ed375517e02379b6d8365676c4d33839b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 349711cfbefe7cb0519e872862f9f54fd24d877850ac63714db59b62f4d7f7008bc39008fb74bca22eebb85c72d1e90e0fdf7c1a5fcb5ace205af63611440579
|
7
|
+
data.tar.gz: 10e8d151d5bd52f464aa92dcd737f1e29354699b66832f17f977b5608b4716532406ba21ac0d83e439732abad9558f46feb0fc0037e06b7ce79169622a36a283
|
@@ -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
CHANGED
@@ -1,15 +1,41 @@
|
|
1
1
|
---
|
2
|
+
require:
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
|
6
|
+
inherit_mode:
|
7
|
+
merge:
|
8
|
+
- Exclude
|
9
|
+
|
10
|
+
AllCops:
|
11
|
+
NewCops: enable
|
12
|
+
Exclude:
|
13
|
+
- 'spec/cronitor_spec.rb'
|
14
|
+
|
2
15
|
Metrics/AbcSize:
|
3
16
|
Enabled: false
|
4
17
|
|
18
|
+
Metrics/ClassLength:
|
19
|
+
Enabled: false
|
20
|
+
|
5
21
|
Metrics/BlockLength:
|
6
22
|
Enabled: false
|
7
23
|
|
8
24
|
Metrics/CyclomaticComplexity:
|
9
25
|
Enabled: false
|
10
26
|
|
27
|
+
Metrics/PerceivedComplexity:
|
28
|
+
Enabled: false
|
29
|
+
|
11
30
|
Metrics/MethodLength:
|
12
31
|
Enabled: false
|
13
32
|
|
14
33
|
Style/Documentation:
|
15
34
|
Enabled: false
|
35
|
+
|
36
|
+
Style/RaiseArgs:
|
37
|
+
EnforcedStyle: compact
|
38
|
+
|
39
|
+
Style/EachWithObject:
|
40
|
+
Enabled: false
|
41
|
+
|
data/README.md
CHANGED
@@ -1,135 +1,230 @@
|
|
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, data pipelines, queue workers, 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.
|
9
8
|
|
10
|
-
##
|
9
|
+
## Documentation
|
10
|
+
See our [API docs](https://cronitor.io/docs/api) for a detailed reference information about the APIs this library uses for configuring monitors and sending telemetry pings.
|
11
11
|
|
12
|
-
|
12
|
+
## Installation
|
13
13
|
|
14
|
-
```
|
15
|
-
gem
|
14
|
+
```
|
15
|
+
gem install cronitor
|
16
16
|
```
|
17
17
|
|
18
|
-
|
18
|
+
## Usage
|
19
19
|
|
20
|
-
|
20
|
+
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` (default: account default) and `Environment` (default: account default).
|
21
21
|
|
22
|
-
|
22
|
+
These can be supplied using the environment variables `CRONITOR_API_KEY`, `CRONITOR_API_VERSION`, `CRONITOR_ENVIRONMENT` or set directly on the cronitor object.
|
23
23
|
|
24
|
-
|
24
|
+
```ruby
|
25
|
+
require 'cronitor'
|
25
26
|
|
26
|
-
|
27
|
+
Cronitor.api_key = 'apiKey123'
|
28
|
+
Cronitor.api_version = '2020-10-01'
|
29
|
+
Cronitor.environment = 'staging'
|
30
|
+
```
|
31
|
+
|
32
|
+
You can also use a YAML config file to manage all of your monitors (_see Create and Update Monitors section below_). The path to this file can be supplied using the enviroment variable `CRONITOR_CONFIG` or call `cronitor.read_config()`.
|
27
33
|
|
34
|
+
```ruby
|
35
|
+
require 'cronitor'
|
28
36
|
|
29
|
-
|
37
|
+
Cronitor.read_config('./path/to/cronitor.yaml')
|
38
|
+
```
|
30
39
|
|
31
|
-
You need to set Cronitor Token in order to create a monitor
|
32
40
|
|
33
|
-
|
41
|
+
### Monitor Any Block
|
42
|
+
|
43
|
+
The quickest way to start using this library is to wrap a block of code with the `#job` helper. It will report the start time, end time, and exit state to Cronitor. If an exception is raised, the stack trace will be included in the failure message.
|
34
44
|
|
35
45
|
```ruby
|
36
46
|
require 'cronitor'
|
37
47
|
|
38
|
-
Cronitor.
|
39
|
-
|
48
|
+
Cronitor.job 'warehouse-replenishmenth-report' do
|
49
|
+
ReplenishmentReport.new(Date.today).run()
|
40
50
|
end
|
41
51
|
```
|
42
52
|
|
43
|
-
|
53
|
+
### Sending Telemetry Events
|
44
54
|
|
45
|
-
|
46
|
-
|
47
|
-
|
55
|
+
If you want finer control over when/how [telemetry pings](https://cronitor.io/docs/telemetry-api) are sent,
|
56
|
+
you can instantiate a monitor and call `#ping`.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
require 'cronitor'
|
48
60
|
|
49
|
-
# bash
|
50
|
-
export CRONITOR_TOKEN='token'
|
51
|
-
```
|
52
61
|
|
53
|
-
|
62
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
54
63
|
|
55
|
-
|
64
|
+
monitor.ping # a basic heartbeat event
|
56
65
|
|
57
|
-
|
66
|
+
# optional params can be passed as kwargs
|
67
|
+
# complete list - https://cronitor.io/docs/telemetry-api#parameters
|
58
68
|
|
59
|
-
|
69
|
+
monitor.ping(state: 'run', env: 'staging') # a job/process has started in a staging environment
|
70
|
+
|
71
|
+
# a job/process has completed - include metrics for cronitor to record
|
72
|
+
monitor.ping(state: 'complete', metrics: {count: 1000, error_count: 17})
|
73
|
+
```
|
74
|
+
|
75
|
+
### Pause, Reset, Delete
|
60
76
|
|
61
77
|
```ruby
|
62
78
|
require 'cronitor'
|
63
79
|
|
64
|
-
|
65
|
-
name: 'My Fancy Monitor',
|
66
|
-
type: 'heartbeat', # Optional: the gem defaults to this; the other value, 'healthcheck', is not yet supported by this gem
|
67
|
-
notifications: {
|
68
|
-
emails: ['test@example.com'],
|
69
|
-
slack: [],
|
70
|
-
pagerduty: [],
|
71
|
-
phones: [],
|
72
|
-
webhooks: []
|
73
|
-
},
|
74
|
-
rules: [
|
75
|
-
{
|
76
|
-
rule_type: 'run_ping_not_received',
|
77
|
-
value: 5,
|
78
|
-
time_unit: 'seconds'
|
79
|
-
}
|
80
|
-
],
|
81
|
-
note: 'A human-friendly description of this monitor'
|
82
|
-
}
|
83
|
-
|
84
|
-
# The token parameter is optional; if omittted, ENV['CRONITOR_TOKEN'] will be used if not configured
|
85
|
-
my_monitor = Cronitor.new token: 'api_token', opts: monitor_options
|
86
|
-
```
|
80
|
+
monitor = Cronitor::Monitor.new('heartbeat-monitor')
|
87
81
|
|
88
|
-
|
82
|
+
monitor.pause(24) # pause alerting for 24 hours
|
83
|
+
monitor.unpause # alias for .pause(0)
|
84
|
+
monitor.ok # manually reset to a passing state alias for monitor.ping({state: ok})
|
85
|
+
monitor.delete # destroy the monitor
|
86
|
+
```
|
89
87
|
|
90
|
-
|
88
|
+
## Create and Update Monitors
|
91
89
|
|
92
|
-
|
90
|
+
You can create monitors programatically using the `Monitor` object.
|
91
|
+
For details on all of the attributes that can be set see the [Monitor API](https://cronitor.io/docs/monitor-api) documentation.
|
93
92
|
|
94
|
-
Once you’ve created a monitor, you can continue to use the existing instance of the object to ping the monitor that your task status: `run`, `complete`, or `fail`.
|
95
93
|
|
96
94
|
```ruby
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
require 'cronitor'
|
96
|
+
|
97
|
+
monitors = Cronitor::Monitor.put([
|
98
|
+
{
|
99
|
+
type: 'job',
|
100
|
+
key: 'send-customer-invoices',
|
101
|
+
schedule: '0 0 * * *',
|
102
|
+
assertions: [
|
103
|
+
'metric.duration < 5 min'
|
104
|
+
],
|
105
|
+
notify: ['devops-alerts-slack']
|
106
|
+
},
|
107
|
+
{
|
108
|
+
type: 'synthetic',
|
109
|
+
key: 'Orders Api Uptime',
|
110
|
+
schedule: 'every 45 seconds',
|
111
|
+
assertions: [
|
112
|
+
'response.code = 200',
|
113
|
+
'response.time < 1.5s',
|
114
|
+
'response.json "open_orders" < 2000'
|
115
|
+
]
|
116
|
+
}
|
117
|
+
])
|
100
118
|
```
|
101
119
|
|
102
|
-
|
120
|
+
You can also manage all of your monitors via a YAML config file.
|
121
|
+
This can be version controlled and synced to Cronitor as part of
|
122
|
+
a deployment process or system update.
|
103
123
|
|
104
|
-
|
124
|
+
```ruby
|
125
|
+
require 'cronitor'
|
105
126
|
|
106
|
-
|
127
|
+
# read config file and set credentials (if included).
|
128
|
+
Cronitor.read_config('./cronitor.yaml')
|
107
129
|
|
108
|
-
|
130
|
+
# sync config file's monitors to Cronitor.
|
131
|
+
Cronitor.apply_config
|
109
132
|
|
110
|
-
|
111
|
-
|
133
|
+
# send config file's monitors to Cronitor to validate correctness.
|
134
|
+
# monitors will not be saved.
|
135
|
+
Cronitor.validate_config
|
112
136
|
```
|
113
137
|
|
114
|
-
The aforementioned ping methods can now be used.
|
115
138
|
|
116
|
-
|
139
|
+
The `cronitor.yaml` file accepts the following attributes:
|
140
|
+
|
141
|
+
```yaml
|
142
|
+
api_key: 'optionally read Cronitor api_key from here'
|
143
|
+
api_version: 'optionally read Cronitor api_version from here'
|
144
|
+
environment: 'optionally set an environment for telemetry pings'
|
145
|
+
|
146
|
+
# configure all of your monitors with type "job"
|
147
|
+
# you may omit the type attribute and the key
|
148
|
+
# of each object will be set as the monitor key
|
149
|
+
jobs:
|
150
|
+
nightly-database-backup:
|
151
|
+
schedule: 0 0 * * *
|
152
|
+
notify:
|
153
|
+
- devops-alert-pagerduty
|
154
|
+
assertions:
|
155
|
+
- metric.duration < 5 minutes
|
156
|
+
|
157
|
+
send-welcome-email:
|
158
|
+
schedule: every 10 minutes
|
159
|
+
assertions:
|
160
|
+
- metric.count > 0
|
161
|
+
- metric.duration < 30 seconds
|
162
|
+
|
163
|
+
# configure all of your monitors with type "synthetic"
|
164
|
+
synthetics:
|
165
|
+
cronitor-homepage:
|
166
|
+
request:
|
167
|
+
url: https://cronitor.io
|
168
|
+
regions:
|
169
|
+
- us-east-1
|
170
|
+
- eu-central-1
|
171
|
+
- ap-northeast-1
|
172
|
+
assertions:
|
173
|
+
- response.code = 200
|
174
|
+
- response.time < 2s
|
175
|
+
|
176
|
+
cronitor-telemetry-api:
|
177
|
+
request:
|
178
|
+
url: https://cronitor.link/ping
|
179
|
+
assertions:
|
180
|
+
- response.body contains ok
|
181
|
+
- response.time < .25s
|
182
|
+
|
183
|
+
events:
|
184
|
+
production-deploy:
|
185
|
+
notify:
|
186
|
+
alerts: ['deploys-slack']
|
187
|
+
events: true # send alert when the event occurs
|
117
188
|
|
118
|
-
|
189
|
+
```
|
119
190
|
|
120
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
121
191
|
|
122
192
|
## Contributing
|
123
193
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
194
|
+
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).
|
195
|
+
|
196
|
+
### To contribute
|
197
|
+
|
198
|
+
Fork, then clone the repo:
|
199
|
+
|
200
|
+
git clone git@github.com:your-username/cronitor-ruby.git
|
201
|
+
|
202
|
+
|
203
|
+
Set up your machine:
|
204
|
+
|
205
|
+
bin/setup
|
206
|
+
|
207
|
+
|
208
|
+
Make sure the tests pass:
|
209
|
+
|
210
|
+
rake spec
|
211
|
+
|
212
|
+
|
213
|
+
Make your change. You can experiment using:
|
214
|
+
|
215
|
+
bin/console
|
216
|
+
|
217
|
+
|
218
|
+
Add tests for your change. Make the tests pass:
|
219
|
+
|
220
|
+
rake spec
|
221
|
+
|
222
|
+
Push to your fork and [submit a pull request]( https://github.com/cronitorio/cronitor-ruby/compare/)
|
223
|
+
|
129
224
|
|
130
225
|
## Release a new version
|
131
226
|
|
132
|
-
The
|
227
|
+
The bump gem makes this easy:
|
133
228
|
|
134
229
|
1. `rake bump:(major|minor|patch|pre)`
|
135
|
-
2. `rake release`
|
230
|
+
2. `rake release`
|
data/Rakefile
CHANGED
@@ -3,7 +3,13 @@
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
require 'bump/tasks'
|
6
|
+
require 'rubocop/rake_task'
|
6
7
|
|
7
8
|
RSpec::Core::RakeTask.new(:spec)
|
8
9
|
|
10
|
+
RuboCop::RakeTask.new do |task|
|
11
|
+
task.requires << 'rubocop-rake'
|
12
|
+
task.requires << 'rubocop-rspec'
|
13
|
+
end
|
14
|
+
|
9
15
|
task default: :spec
|
data/cronitor.gemspec
CHANGED
@@ -7,11 +7,13 @@ require 'cronitor/version'
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = 'cronitor'
|
9
9
|
spec.version = Cronitor::VERSION
|
10
|
-
spec.authors = ['Jeff Byrnes']
|
11
|
-
spec.email = ['thejeffbyrnes@gmail.com']
|
10
|
+
spec.authors = ['Jeff Byrnes', 'August Flanagan']
|
11
|
+
spec.email = ['thejeffbyrnes@gmail.com', 'august@cronitor.io']
|
12
12
|
|
13
13
|
spec.summary = 'An interface for the Cronitor API'
|
14
|
-
spec.homepage = 'https://github.com/
|
14
|
+
spec.homepage = 'https://github.com/cronitorio/cronitor-ruby'
|
15
|
+
|
16
|
+
spec.required_ruby_version = '>= 2.4'
|
15
17
|
|
16
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
19
|
f.match(%r{^(test|spec|features)/})
|
@@ -20,11 +22,16 @@ Gem::Specification.new do |spec|
|
|
20
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
23
|
spec.require_paths = ['lib']
|
22
24
|
|
25
|
+
spec.add_runtime_dependency 'httparty'
|
26
|
+
|
23
27
|
spec.add_development_dependency 'bump', '~> 0.1'
|
24
28
|
spec.add_development_dependency 'bundler'
|
25
29
|
spec.add_development_dependency 'pry', '~> 0.10'
|
26
30
|
spec.add_development_dependency 'rake'
|
27
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'
|
28
35
|
spec.add_development_dependency 'sinatra', '~> 2.0'
|
29
36
|
spec.add_development_dependency 'webmock', '~> 3.1'
|
30
37
|
end
|
data/lib/cronitor.rb
CHANGED
@@ -1,138 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'cronitor/error'
|
3
|
+
require 'logger'
|
5
4
|
require 'json'
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
|
9
|
-
|
10
|
-
attr_accessor :token, :opts, :code
|
11
|
-
API_URL = 'https://cronitor.io/v3'
|
12
|
-
PING_URL = 'https://cronitor.link'
|
13
|
-
|
14
|
-
class << self
|
15
|
-
attr_accessor :default_token
|
16
|
-
|
17
|
-
def configure(&block)
|
18
|
-
block.call(self)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(token: ENV['CRONITOR_TOKEN'], opts: {}, code: nil)
|
23
|
-
@token = token || self.class.default_token
|
24
|
-
@opts = opts
|
25
|
-
@code = code
|
5
|
+
require 'httparty'
|
6
|
+
require 'socket'
|
7
|
+
require 'time'
|
8
|
+
require 'yaml'
|
26
9
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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')"
|
32
22
|
)
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
exists? @opts[:name] if @opts.key? :name
|
39
|
-
|
40
|
-
# README: Per Cronitor API v2, we need to specify a type. The "heartbeat"
|
41
|
-
# type corresponds to what the v1 API offered by default
|
42
|
-
# We allow other values to be injected, and let the API handle
|
43
|
-
# any errors.
|
44
|
-
@opts[:type] = 'heartbeat' unless @opts[:type]
|
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)
|
45
28
|
end
|
46
29
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def create
|
51
|
-
uri = URI.parse "#{API_URL}/monitors"
|
52
|
-
|
53
|
-
http = Net::HTTP.new uri.host, uri.port
|
54
|
-
http.use_ssl = uri.scheme == 'https'
|
55
|
-
|
56
|
-
request = Net::HTTP::Post.new uri.path, default_headers
|
57
|
-
request.basic_auth token, nil
|
58
|
-
request.content_type = 'application/json'
|
59
|
-
request.body = JSON.generate opts
|
60
|
-
|
61
|
-
response = http.request request
|
62
|
-
|
63
|
-
@code = JSON.parse(response.body).fetch 'code' if valid? response
|
64
|
-
end
|
65
|
-
|
66
|
-
def exists?(name)
|
67
|
-
uri = URI.parse "#{API_URL}/monitors/#{URI.escape name}"
|
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]
|
68
33
|
|
69
|
-
|
70
|
-
http.use_ssl = uri.scheme == 'https'
|
34
|
+
return unless output
|
71
35
|
|
72
|
-
|
73
|
-
|
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
|
74
41
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@code = JSON.parse(response.body).fetch 'code'
|
80
|
-
@opts = JSON.parse(response.body)
|
81
|
-
|
82
|
-
true
|
83
|
-
end
|
42
|
+
unless to_parse.is_a?(Hash)
|
43
|
+
raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
|
44
|
+
end
|
84
45
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
46
|
+
to_parse.each do |key, m|
|
47
|
+
m['key'] = key
|
48
|
+
m['type'] = t
|
49
|
+
monitors << m
|
50
|
+
end
|
89
51
|
end
|
90
|
-
|
91
|
-
|
92
|
-
http.use_ssl = uri.scheme == 'https'
|
93
|
-
|
94
|
-
request = Net::HTTP::Get.new uri, default_headers
|
95
|
-
|
96
|
-
response = http.request request
|
97
|
-
|
98
|
-
valid? response
|
52
|
+
conf['monitors'] = monitors
|
53
|
+
conf
|
99
54
|
end
|
100
55
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
error_msg JSON.parse(response.body)
|
108
|
-
else
|
109
|
-
"Something else has gone awry. HTTP status: #{response.code}"
|
110
|
-
end
|
111
|
-
|
112
|
-
raise Cronitor::Error, msg
|
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)
|
113
62
|
end
|
114
63
|
|
115
|
-
def
|
116
|
-
|
117
|
-
if value.respond_to? 'each'
|
118
|
-
value.each do |error_msg|
|
119
|
-
msg << "#{opt}: #{error_msg}"
|
120
|
-
end
|
121
|
-
else
|
122
|
-
msg << "#{opt}: #{value}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
msg.join ' '
|
64
|
+
def self.validate_config
|
65
|
+
apply_config(rollback: true)
|
127
66
|
end
|
128
67
|
|
129
|
-
def
|
130
|
-
|
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
|
131
80
|
end
|
132
81
|
|
133
|
-
def
|
134
|
-
|
135
|
-
h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
136
|
-
end
|
82
|
+
def self.monitor_api_url
|
83
|
+
'https://cronitor.io/api/monitors'
|
137
84
|
end
|
138
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_EVENT = 'event'
|
6
|
+
TYPE_SYNTHETIC = 'synthetic'
|
7
|
+
MONITOR_TYPES = [TYPE_JOB, TYPE_EVENT, TYPE_SYNTHETIC].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,15 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cronitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Byrnes
|
8
|
+
- August Flanagan
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
13
28
|
- !ruby/object:Gem::Dependency
|
14
29
|
name: bump
|
15
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +95,48 @@ dependencies:
|
|
80
95
|
- - "~>"
|
81
96
|
- !ruby/object:Gem::Version
|
82
97
|
version: '3.3'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rubocop
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '1.8'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1.8'
|
112
|
+
- !ruby/object:Gem::Dependency
|
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
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - "~>"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '2.1'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '2.1'
|
83
140
|
- !ruby/object:Gem::Dependency
|
84
141
|
name: sinatra
|
85
142
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,15 +168,17 @@ dependencies:
|
|
111
168
|
description:
|
112
169
|
email:
|
113
170
|
- thejeffbyrnes@gmail.com
|
171
|
+
- august@cronitor.io
|
114
172
|
executables: []
|
115
173
|
extensions: []
|
116
174
|
extra_rdoc_files: []
|
117
175
|
files:
|
118
176
|
- ".editorconfig"
|
177
|
+
- ".github/workflows/publish.yml"
|
178
|
+
- ".github/workflows/test.yml"
|
119
179
|
- ".gitignore"
|
120
180
|
- ".rspec"
|
121
181
|
- ".rubocop.yml"
|
122
|
-
- ".travis.yml"
|
123
182
|
- Gemfile
|
124
183
|
- LICENSE.txt
|
125
184
|
- README.md
|
@@ -128,9 +187,11 @@ files:
|
|
128
187
|
- bin/setup
|
129
188
|
- cronitor.gemspec
|
130
189
|
- lib/cronitor.rb
|
190
|
+
- lib/cronitor/config.rb
|
131
191
|
- lib/cronitor/error.rb
|
192
|
+
- lib/cronitor/monitor.rb
|
132
193
|
- lib/cronitor/version.rb
|
133
|
-
homepage: https://github.com/
|
194
|
+
homepage: https://github.com/cronitorio/cronitor-ruby
|
134
195
|
licenses: []
|
135
196
|
metadata: {}
|
136
197
|
post_install_message:
|
@@ -141,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
202
|
requirements:
|
142
203
|
- - ">="
|
143
204
|
- !ruby/object:Gem::Version
|
144
|
-
version: '
|
205
|
+
version: '2.4'
|
145
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
207
|
requirements:
|
147
208
|
- - ">="
|