cronitor 2.0.0 → 4.1.1
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 -9
- data/lib/cronitor.rb +64 -107
- 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 +87 -26
- data/.travis.yml +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 70d39a60f81039830a50c36c9885a6021155dfdef29403b2b28335f8be8fd2f8
|
4
|
+
data.tar.gz: 26d1637753bfbea371b1dd5add7752cb36b9a2a20d0c5f2a66c760813fde83b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ad94427bb731459e4a41463b0435a2afc6e904ad15a76a57d50557d77285931bca7773b537b9eb5433b382364ab0161e87f03b326f5dca5faa4997e70f8c63d
|
7
|
+
data.tar.gz: beb4775ed796685a6d1c90bb88c692cf8d2a34f834c858449138afffbcf53d258f84ff791c265d89e05db287d8259a9a7087f32c660410f99b40d965f49a0e19
|
@@ -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`, `heartbeats`. 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,11 +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
|
-
|
23
|
-
spec.add_development_dependency '
|
25
|
+
spec.add_runtime_dependency 'httparty'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bump', '~> 0.1'
|
28
|
+
spec.add_development_dependency 'bundler'
|
24
29
|
spec.add_development_dependency 'pry', '~> 0.10'
|
25
|
-
spec.add_development_dependency '
|
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'
|
26
35
|
spec.add_development_dependency 'sinatra', '~> 2.0'
|
27
|
-
spec.add_development_dependency '
|
36
|
+
spec.add_development_dependency 'webmock', '~> 3.1'
|
28
37
|
end
|
data/lib/cronitor.rb
CHANGED
@@ -1,130 +1,87 @@
|
|
1
|
-
|
2
|
-
require 'cronitor/error'
|
3
|
-
require 'json'
|
4
|
-
require 'net/http'
|
5
|
-
require 'uri'
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@token = token
|
14
|
-
@opts = opts
|
15
|
-
@code = code
|
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
|
29
|
-
end
|
30
|
-
|
31
|
-
create if @code.nil?
|
32
|
-
end
|
33
|
-
|
34
|
-
def create
|
35
|
-
uri = URI.parse "#{API_URL}/monitors"
|
36
|
-
|
37
|
-
http = Net::HTTP.new uri.host, uri.port
|
38
|
-
http.use_ssl = uri.scheme == 'https'
|
39
|
-
|
40
|
-
request = Net::HTTP::Post.new uri.path, default_headers
|
41
|
-
request.basic_auth token, nil
|
42
|
-
request.content_type = 'application/json'
|
43
|
-
request.body = JSON.generate opts
|
44
|
-
|
45
|
-
response = http.request request
|
46
|
-
|
47
|
-
@code = JSON.parse(response.body).fetch 'code' if valid? response
|
48
|
-
end
|
49
|
-
|
50
|
-
def exists?(name)
|
51
|
-
uri = URI.parse "#{API_URL}/monitors/#{URI.escape name}"
|
52
|
-
|
53
|
-
http = Net::HTTP.new uri.host, uri.port
|
54
|
-
http.use_ssl = uri.scheme == 'https'
|
55
|
-
|
56
|
-
request = Net::HTTP::Get.new uri.path, default_headers
|
57
|
-
request.basic_auth token, nil
|
58
|
-
|
59
|
-
response = http.request request
|
60
|
-
|
61
|
-
return false unless response.is_a? Net::HTTPSuccess
|
62
|
-
|
63
|
-
@code = JSON.parse(response.body).fetch 'code'
|
64
|
-
|
65
|
-
true
|
66
|
-
end
|
67
|
-
|
68
|
-
def ping(type, msg = nil)
|
69
|
-
uri = URI.parse "#{PING_URL}/#{URI.escape code}/#{URI.escape type}"
|
70
|
-
if %w[run complete fail].include?(type) && !msg.nil?
|
71
|
-
uri.query = URI.encode_www_form 'msg' => msg
|
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)
|
72
28
|
end
|
73
29
|
|
74
|
-
|
75
|
-
|
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]
|
76
33
|
|
77
|
-
|
34
|
+
return unless output
|
78
35
|
|
79
|
-
|
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
|
80
41
|
|
81
|
-
|
82
|
-
|
42
|
+
unless to_parse.is_a?(Hash)
|
43
|
+
raise ConfigurationError.new('A Hash with keys corresponding to monitor keys is expected.')
|
44
|
+
end
|
83
45
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"#{rule[:time_unit]}"
|
46
|
+
to_parse.each do |key, m|
|
47
|
+
m['key'] = key
|
48
|
+
m['type'] = t
|
49
|
+
monitors << m
|
89
50
|
end
|
90
51
|
end
|
52
|
+
conf['monitors'] = monitors
|
53
|
+
conf
|
91
54
|
end
|
92
55
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
error_msg JSON.parse(response.body)
|
100
|
-
else
|
101
|
-
"Something else has gone awry. HTTP status: #{response.code}"
|
102
|
-
end
|
103
|
-
|
104
|
-
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)
|
105
62
|
end
|
106
63
|
|
107
|
-
def
|
108
|
-
|
109
|
-
if value.respond_to? 'each'
|
110
|
-
value.each do |error_msg|
|
111
|
-
msg << "#{opt}: #{error_msg}"
|
112
|
-
end
|
113
|
-
else
|
114
|
-
msg << "#{opt}: #{value}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
msg.join ' '
|
64
|
+
def self.validate_config
|
65
|
+
apply_config(rollback: true)
|
119
66
|
end
|
120
67
|
|
121
|
-
def
|
122
|
-
|
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
|
123
80
|
end
|
124
81
|
|
125
|
-
def
|
126
|
-
|
127
|
-
h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
128
|
-
end
|
82
|
+
def self.monitor_api_url
|
83
|
+
'https://cronitor.io/api/monitors'
|
129
84
|
end
|
130
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: 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,43 +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.1
|
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-28 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
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'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bump
|
15
30
|
requirement: !ruby/object:Gem::Requirement
|
16
31
|
requirements:
|
17
32
|
- - "~>"
|
18
33
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
34
|
+
version: '0.1'
|
20
35
|
type: :development
|
21
36
|
prerelease: false
|
22
37
|
version_requirements: !ruby/object:Gem::Requirement
|
23
38
|
requirements:
|
24
39
|
- - "~>"
|
25
40
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
41
|
+
version: '0.1'
|
27
42
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
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
|
29
58
|
requirement: !ruby/object:Gem::Requirement
|
30
59
|
requirements:
|
31
60
|
- - "~>"
|
32
61
|
- !ruby/object:Gem::Version
|
33
|
-
version: '10
|
62
|
+
version: '0.10'
|
34
63
|
type: :development
|
35
64
|
prerelease: false
|
36
65
|
version_requirements: !ruby/object:Gem::Requirement
|
37
66
|
requirements:
|
38
67
|
- - "~>"
|
39
68
|
- !ruby/object:Gem::Version
|
40
|
-
version: '10
|
69
|
+
version: '0.10'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
41
84
|
- !ruby/object:Gem::Dependency
|
42
85
|
name: rspec
|
43
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,33 +96,47 @@ dependencies:
|
|
53
96
|
- !ruby/object:Gem::Version
|
54
97
|
version: '3.3'
|
55
98
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
99
|
+
name: rubocop
|
57
100
|
requirement: !ruby/object:Gem::Requirement
|
58
101
|
requirements:
|
59
102
|
- - "~>"
|
60
103
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
104
|
+
version: '1.8'
|
62
105
|
type: :development
|
63
106
|
prerelease: false
|
64
107
|
version_requirements: !ruby/object:Gem::Requirement
|
65
108
|
requirements:
|
66
109
|
- - "~>"
|
67
110
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
111
|
+
version: '1.8'
|
69
112
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
113
|
+
name: rubocop-rake
|
71
114
|
requirement: !ruby/object:Gem::Requirement
|
72
115
|
requirements:
|
73
116
|
- - "~>"
|
74
117
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
118
|
+
version: 0.5.1
|
76
119
|
type: :development
|
77
120
|
prerelease: false
|
78
121
|
version_requirements: !ruby/object:Gem::Requirement
|
79
122
|
requirements:
|
80
123
|
- - "~>"
|
81
124
|
- !ruby/object:Gem::Version
|
82
|
-
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
|
@@ -95,30 +152,33 @@ dependencies:
|
|
95
152
|
- !ruby/object:Gem::Version
|
96
153
|
version: '2.0'
|
97
154
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
155
|
+
name: webmock
|
99
156
|
requirement: !ruby/object:Gem::Requirement
|
100
157
|
requirements:
|
101
158
|
- - "~>"
|
102
159
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
160
|
+
version: '3.1'
|
104
161
|
type: :development
|
105
162
|
prerelease: false
|
106
163
|
version_requirements: !ruby/object:Gem::Requirement
|
107
164
|
requirements:
|
108
165
|
- - "~>"
|
109
166
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
111
|
-
description:
|
167
|
+
version: '3.1'
|
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
182
|
- Gemfile
|
123
183
|
- LICENSE.txt
|
124
184
|
- README.md
|
@@ -127,12 +187,14 @@ files:
|
|
127
187
|
- bin/setup
|
128
188
|
- cronitor.gemspec
|
129
189
|
- lib/cronitor.rb
|
190
|
+
- lib/cronitor/config.rb
|
130
191
|
- lib/cronitor/error.rb
|
192
|
+
- lib/cronitor/monitor.rb
|
131
193
|
- lib/cronitor/version.rb
|
132
|
-
homepage: https://github.com/
|
194
|
+
homepage: https://github.com/cronitorio/cronitor-ruby
|
133
195
|
licenses: []
|
134
196
|
metadata: {}
|
135
|
-
post_install_message:
|
197
|
+
post_install_message:
|
136
198
|
rdoc_options: []
|
137
199
|
require_paths:
|
138
200
|
- lib
|
@@ -140,16 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
202
|
requirements:
|
141
203
|
- - ">="
|
142
204
|
- !ruby/object:Gem::Version
|
143
|
-
version: '
|
205
|
+
version: '2.5'
|
144
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
207
|
requirements:
|
146
208
|
- - ">="
|
147
209
|
- !ruby/object:Gem::Version
|
148
210
|
version: '0'
|
149
211
|
requirements: []
|
150
|
-
|
151
|
-
|
152
|
-
signing_key:
|
212
|
+
rubygems_version: 3.1.4
|
213
|
+
signing_key:
|
153
214
|
specification_version: 4
|
154
215
|
summary: An interface for the Cronitor API
|
155
216
|
test_files: []
|
data/.travis.yml
DELETED