systemd_mon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +2 -0
- data/bin/systemd_mon +4 -0
- data/lib/systemd_mon.rb +5 -0
- data/lib/systemd_mon/callback_manager.rb +39 -0
- data/lib/systemd_mon/cli.rb +81 -0
- data/lib/systemd_mon/dbus_manager.rb +31 -0
- data/lib/systemd_mon/dbus_unit.rb +57 -0
- data/lib/systemd_mon/error.rb +17 -0
- data/lib/systemd_mon/formatters/base.rb +18 -0
- data/lib/systemd_mon/formatters/state_table_formatter.rb +32 -0
- data/lib/systemd_mon/logger.rb +33 -0
- data/lib/systemd_mon/monitor.rb +98 -0
- data/lib/systemd_mon/notification.rb +30 -0
- data/lib/systemd_mon/notification_centre.rb +77 -0
- data/lib/systemd_mon/notifier_loader.rb +21 -0
- data/lib/systemd_mon/notifiers/base.rb +39 -0
- data/lib/systemd_mon/notifiers/email.rb +60 -0
- data/lib/systemd_mon/notifiers/slack.rb +92 -0
- data/lib/systemd_mon/state.rb +33 -0
- data/lib/systemd_mon/state_change.rb +113 -0
- data/lib/systemd_mon/state_value.rb +48 -0
- data/lib/systemd_mon/unit_with_state.rb +33 -0
- data/lib/systemd_mon/version.rb +3 -0
- data/systemd_mon.gemspec +24 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 71beedd875598eeb882df66a2a2982aad63fffe3
|
4
|
+
data.tar.gz: e08a8386c4489f8f42461ed8aa5bf2a65eb78bf6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 016247aaa8c34f2cb6eaddd14459bc58d52ccd12e32557ca3a5d4e5a30c06e5222db72393f5d630d293ec69533897d7a5d6fdd7d1a161bec2a02ae5acaf43ed2
|
7
|
+
data.tar.gz: ca44541313c92f37e790f15ba07862885288d1d4e026bdb1560f681f1bc3b3cda721ec6b608b18c6507a0b65cf763bc75bd039f010e6818243e020b71f28c29b
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jon Cairns
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# SystemdMon
|
2
|
+
|
3
|
+
Monitor systemd units and trigger alerts for failed states. The command line tool runs as a daemon, using dbus to get notifications of changes to systemd services. If a service enters a failed state, or returns from a failed state to an active state, notifications will be triggered.
|
4
|
+
|
5
|
+
Built-in notifications include email and slack, but more can be added via the ruby API.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install the gem using:
|
10
|
+
|
11
|
+
gem install systemd_mon
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
To run the command line tool, you will first need to create a YAML configuration file to specify which systemd units you want to monitor, and which notifications you want to trigger. A full example looks like this:
|
16
|
+
|
17
|
+
```yaml
|
18
|
+
---
|
19
|
+
notifiers:
|
20
|
+
# These are options passed to the 'mail' gem
|
21
|
+
email:
|
22
|
+
address: smtp.gmail.com
|
23
|
+
port: 587
|
24
|
+
domain: mydomain.com
|
25
|
+
user_name: "user@mydomain.com"
|
26
|
+
password: "supersecr3t"
|
27
|
+
authentication: "plain"
|
28
|
+
enable_starttls_auto: true
|
29
|
+
slack:
|
30
|
+
team: myteam
|
31
|
+
token: supersecr3ttoken
|
32
|
+
channel: mychannel
|
33
|
+
username: doge
|
34
|
+
units:
|
35
|
+
- unicorn.service
|
36
|
+
- nginx.service
|
37
|
+
- sidekiq.service
|
38
|
+
```
|
39
|
+
|
40
|
+
Then start the command line tool with:
|
41
|
+
|
42
|
+
$ systemd_mon path/to/systemd_mon.yml
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
1. Fork it ( https://github.com/joonty/systemd_mon/fork )
|
47
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
48
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
49
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
50
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/systemd_mon
ADDED
data/lib/systemd_mon.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'systemd_mon/unit_with_state'
|
2
|
+
|
3
|
+
module SystemdMon
|
4
|
+
class CallbackManager
|
5
|
+
def initialize(queue)
|
6
|
+
self.queue = queue
|
7
|
+
self.states = Hash.new { |h, u| h[u] = UnitWithState.new(u) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def start(change_callback, each_state_change_callback)
|
11
|
+
loop do
|
12
|
+
unit, state = queue.deq
|
13
|
+
Logger.debug { state }
|
14
|
+
unit_state = states[unit]
|
15
|
+
unit_state << state
|
16
|
+
|
17
|
+
if each_state_change_callback
|
18
|
+
with_error_handling { each_state_change_callback.call(unit_state) }
|
19
|
+
end
|
20
|
+
|
21
|
+
if change_callback && unit_state.state_change.important?
|
22
|
+
with_error_handling { change_callback.call(unit_state) }
|
23
|
+
end
|
24
|
+
|
25
|
+
unit_state.reset! if unit_state.state_change.important?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_error_handling
|
30
|
+
yield
|
31
|
+
rescue => e
|
32
|
+
Logger.error "Uncaught exception (#{e.class}) in callback: #{e.message}"
|
33
|
+
Logger.debug_error { "\n\t#{e.backtrace.join("\n\t")}\n" }
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
attr_accessor :queue, :states
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'systemd_mon'
|
3
|
+
require 'systemd_mon/monitor'
|
4
|
+
require 'systemd_mon/error'
|
5
|
+
require 'systemd_mon/dbus_manager'
|
6
|
+
|
7
|
+
module SystemdMon
|
8
|
+
class CLI
|
9
|
+
def initialize
|
10
|
+
self.me = "systemd_mon"
|
11
|
+
self.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
yaml_config_file = ARGV.first
|
16
|
+
self.options = load_and_validate_options(yaml_config_file)
|
17
|
+
self.verbose = options['verbose'] || false
|
18
|
+
Logger.verbose = verbose
|
19
|
+
|
20
|
+
start_monitor
|
21
|
+
|
22
|
+
rescue SystemdMon::Error => e
|
23
|
+
err_string = e.message
|
24
|
+
if verbose
|
25
|
+
err_string << " - #{e.original.message} (#{e.original.class})"
|
26
|
+
err_string << "\n\t#{e.original.backtrace.join("\n\t")}"
|
27
|
+
end
|
28
|
+
fatal_error(err_string)
|
29
|
+
rescue => e
|
30
|
+
err_string = e.message
|
31
|
+
if verbose
|
32
|
+
err_string << " (#{e.class})"
|
33
|
+
err_string << "\n\t#{e.backtrace.join("\n\t")}"
|
34
|
+
end
|
35
|
+
fatal_error(err_string)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def start_monitor
|
40
|
+
monitor = Monitor.new(DBusManager.new)
|
41
|
+
|
42
|
+
# Load units to monitor
|
43
|
+
monitor.register_units options['units']
|
44
|
+
|
45
|
+
options['notifiers'].each do |name, notifier_options|
|
46
|
+
klass = NotifierLoader.new.get_class(name)
|
47
|
+
monitor.add_notifier klass.new(notifier_options)
|
48
|
+
end
|
49
|
+
|
50
|
+
monitor.start
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_and_validate_options(yaml_config_file)
|
54
|
+
options = load_options(yaml_config_file)
|
55
|
+
|
56
|
+
unless options.has_key?('notifiers') && options['notifiers'].any?
|
57
|
+
fatal_error("no notifiers have been defined, there is no reason to continue")
|
58
|
+
end
|
59
|
+
unless options.has_key?('units') && options['units'].any?
|
60
|
+
fatal_error("no units have been added for watching, there is no reason to continue")
|
61
|
+
end
|
62
|
+
options
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_options(yaml_config_file)
|
66
|
+
unless yaml_config_file && File.exists?(yaml_config_file)
|
67
|
+
fatal_error "First argument must be a path to a YAML configuration file"
|
68
|
+
end
|
69
|
+
|
70
|
+
YAML.load_file(yaml_config_file)
|
71
|
+
end
|
72
|
+
|
73
|
+
def fatal_error(message, code = 255)
|
74
|
+
$stderr.puts " #{me} error: #{message}"
|
75
|
+
exit code
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
attr_accessor :verbose, :options, :me
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dbus'
|
2
|
+
require 'systemd_mon/error'
|
3
|
+
require 'systemd_mon/dbus_unit'
|
4
|
+
|
5
|
+
module SystemdMon
|
6
|
+
class DBusManager
|
7
|
+
def initialize
|
8
|
+
self.dbus = DBus::SystemBus.instance
|
9
|
+
self.systemd_service = dbus.service("org.freedesktop.systemd1")
|
10
|
+
self.systemd_object = systemd_service.object("/org/freedesktop/systemd1")
|
11
|
+
systemd_object.introspect
|
12
|
+
systemd_object.Subscribe
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_unit(unit_name)
|
16
|
+
path = systemd_object.GetUnit(unit_name).first
|
17
|
+
DBusUnit.new(unit_name, path, systemd_service.object(path))
|
18
|
+
rescue DBus::Error
|
19
|
+
raise SystemdMon::UnknownUnitError, "Unknown or unloaded systemd unit '#{unit_name}'"
|
20
|
+
end
|
21
|
+
|
22
|
+
def runner
|
23
|
+
main = DBus::Main.new
|
24
|
+
main << dbus
|
25
|
+
main
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
attr_accessor :systemd_service, :systemd_object, :dbus
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'systemd_mon/state'
|
2
|
+
|
3
|
+
module SystemdMon
|
4
|
+
class DBusUnit
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
IFACE_UNIT = "org.freedesktop.systemd1.Unit"
|
8
|
+
IFACE_PROPS = "org.freedesktop.DBus.Properties"
|
9
|
+
|
10
|
+
def initialize(name, path, dbus_object)
|
11
|
+
self.name = name
|
12
|
+
self.path = path
|
13
|
+
self.dbus_object = dbus_object
|
14
|
+
prepare_dbus_objects!
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_listener!(queue)
|
18
|
+
queue.enq [self, build_state] # initial state
|
19
|
+
dbus_object.on_signal("PropertiesChanged") do |iface|
|
20
|
+
if iface == IFACE_UNIT
|
21
|
+
queue.enq [self, build_state]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_change(&callback)
|
27
|
+
self.change_callback = callback
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_each_state_change(&callback)
|
31
|
+
self.each_state_change_callback = callback
|
32
|
+
end
|
33
|
+
|
34
|
+
def property(name)
|
35
|
+
dbus_object.Get(IFACE_UNIT, name).first
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
attr_accessor :path, :dbus_object, :change_callback, :each_state_change_callback
|
40
|
+
attr_writer :name
|
41
|
+
|
42
|
+
def build_state
|
43
|
+
State.new(
|
44
|
+
property("ActiveState"),
|
45
|
+
property("SubState"),
|
46
|
+
property("LoadState"),
|
47
|
+
property("UnitFileState")
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare_dbus_objects!
|
52
|
+
dbus_object.introspect
|
53
|
+
self.dbus_object.default_iface = IFACE_PROPS
|
54
|
+
self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
|
3
|
+
# Save original exception for use in verbose mode
|
4
|
+
class Error < StandardError
|
5
|
+
attr_reader :original
|
6
|
+
|
7
|
+
def initialize(msg, original=$!)
|
8
|
+
super(msg)
|
9
|
+
@original = original
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MonitorError < Error; end
|
14
|
+
class UnknownUnitError < Error; end
|
15
|
+
class NotificationError < Error; end
|
16
|
+
class NotifierError < Error; end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SystemdMon::Formatters
|
2
|
+
class Base
|
3
|
+
def initialize(unit)
|
4
|
+
self.unit = unit
|
5
|
+
end
|
6
|
+
|
7
|
+
def as_html
|
8
|
+
raise "The formatter #{self.class} does not provide an html formatted string"
|
9
|
+
end
|
10
|
+
|
11
|
+
def as_text
|
12
|
+
raise "The formatter #{self.class} does not provide a plain text string"
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
attr_accessor :unit
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'systemd_mon/formatters/base'
|
2
|
+
module SystemdMon::Formatters
|
3
|
+
class StateTableFormatter < Base
|
4
|
+
def as_text
|
5
|
+
table = render_table
|
6
|
+
lengths = table.transpose.map { |v| v.map(&:length).max }
|
7
|
+
|
8
|
+
full_width = lengths.inject(&:+) + (lengths.length * 3) + 1
|
9
|
+
div = " " + ("-" * full_width) + "\n"
|
10
|
+
s = div.dup
|
11
|
+
table.each do |row|
|
12
|
+
s << " | "
|
13
|
+
row.each_with_index { |col, i|
|
14
|
+
s << col.ljust(lengths[i]) + " | "
|
15
|
+
}
|
16
|
+
s << "\n" + div.dup
|
17
|
+
end
|
18
|
+
s
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def render_table
|
23
|
+
changed = unit.state_change.diff
|
24
|
+
table = []
|
25
|
+
table << ["Time"].concat(changed.map{|v| v.first.display_name})
|
26
|
+
changed.transpose.each do |vals|
|
27
|
+
table << [vals.first.timestamp.strftime("%H:%M:%S.%3N %z")].concat(vals.map{|v| v.value})
|
28
|
+
end
|
29
|
+
table
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
class Logger
|
3
|
+
def self.verbose=(flag)
|
4
|
+
@verbose = flag
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.verbose
|
8
|
+
@verbose
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.debug(message = nil, stream = $stdout)
|
12
|
+
if verbose
|
13
|
+
if block_given?
|
14
|
+
$stdout.puts yield
|
15
|
+
else
|
16
|
+
$stdout.puts message
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(message = nil)
|
22
|
+
$stderr.puts message
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.debug_error(message = nil)
|
26
|
+
debug message, $stderr
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.puts(message = nil)
|
30
|
+
$stdout.puts message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'systemd_mon/logger'
|
3
|
+
require 'systemd_mon/callback_manager'
|
4
|
+
require 'systemd_mon/notification_centre'
|
5
|
+
require 'systemd_mon/notification'
|
6
|
+
require 'systemd_mon/error'
|
7
|
+
|
8
|
+
module SystemdMon
|
9
|
+
class Monitor
|
10
|
+
def initialize(dbus_manager)
|
11
|
+
self.hostname = `hostname`.strip
|
12
|
+
self.dbus_manager = dbus_manager
|
13
|
+
self.units = []
|
14
|
+
self.change_callback = lambda(&method(:unit_change_callback))
|
15
|
+
self.notification_centre = NotificationCentre.new
|
16
|
+
Thread.abort_on_exception = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_notifier(notifier)
|
20
|
+
notification_centre << notifier
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def register_unit(unit_name)
|
25
|
+
self.units << dbus_manager.fetch_unit(unit_name)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_units(*unit_names)
|
30
|
+
self.units.concat unit_names.flatten.map { |unit_name|
|
31
|
+
dbus_manager.fetch_unit(unit_name)
|
32
|
+
}
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_change(&callback)
|
37
|
+
self.change_callback = callback
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_each_state_change(&callback)
|
42
|
+
self.each_state_change_callback = callback
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
startup_check!
|
48
|
+
at_exit { notification_centre.notify_stop! hostname }
|
49
|
+
notification_centre.notify_start! hostname
|
50
|
+
|
51
|
+
Logger.puts "Monitoring changes to #{units.count} units"
|
52
|
+
Logger.debug { " - " + units.map(&:name).join("\n - ") + "\n\n" }
|
53
|
+
Logger.debug { "Using notifiers: #{notification_centre.classes.join(", ")}"}
|
54
|
+
|
55
|
+
state_q = Queue.new
|
56
|
+
|
57
|
+
units.each do |unit|
|
58
|
+
unit.register_listener! state_q
|
59
|
+
end
|
60
|
+
|
61
|
+
[start_callback_thread(state_q),
|
62
|
+
start_dbus_thread].each(&:join)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
attr_accessor :units, :dbus_manager, :change_callback, :each_state_change_callback, :hostname, :notification_centre
|
67
|
+
|
68
|
+
def startup_check!
|
69
|
+
unless units.any?
|
70
|
+
raise MonitorError, "At least one systemd unit should be registered before monitoring can start"
|
71
|
+
end
|
72
|
+
unless notification_centre.any?
|
73
|
+
raise MonitorError, "At least one notifier should be registered before monitoring can start"
|
74
|
+
end
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def start_dbus_thread
|
79
|
+
Thread.new do
|
80
|
+
dbus_manager.runner.run
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def start_callback_thread(state_q)
|
85
|
+
Thread.new do
|
86
|
+
manager = CallbackManager.new(state_q)
|
87
|
+
manager.start change_callback, each_state_change_callback
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def unit_change_callback(unit)
|
92
|
+
Logger.puts "#{unit.name} #{unit.state_change.status_text}: #{unit.state.active} (#{unit.state.sub})"
|
93
|
+
Logger.debug unit.state_change.to_s
|
94
|
+
Logger.puts
|
95
|
+
notification_centre.notify! Notification.new(hostname, unit)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
class Notification
|
3
|
+
attr_reader :unit, :type, :hostname
|
4
|
+
|
5
|
+
def initialize(hostname, unit)
|
6
|
+
self.hostname = hostname
|
7
|
+
self.unit = unit
|
8
|
+
self.type = determine_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.types
|
12
|
+
[:alert, :info, :ok]
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
attr_writer :unit, :type, :hostname
|
17
|
+
|
18
|
+
def determine_type
|
19
|
+
if unit.state_change.ok?
|
20
|
+
if unit.state_change.first.fail?
|
21
|
+
:ok
|
22
|
+
else
|
23
|
+
:info
|
24
|
+
end
|
25
|
+
else
|
26
|
+
:alert
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'systemd_mon/error'
|
2
|
+
require 'systemd_mon/logger'
|
3
|
+
|
4
|
+
module SystemdMon
|
5
|
+
class NotificationCentre
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.notifiers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def classes
|
13
|
+
notifiers.map(&:class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
notifiers.each do |notifier|
|
18
|
+
yield notifier
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_notifier(notifier)
|
23
|
+
unless notifier.respond_to?(:notify!)
|
24
|
+
raise NotifierError, "Notifier #{notifier.class} must respond to 'notify!'"
|
25
|
+
end
|
26
|
+
self.notifiers << notifier
|
27
|
+
end
|
28
|
+
|
29
|
+
def notify_start!(hostname)
|
30
|
+
each_notifier do |notifier|
|
31
|
+
if notifier.respond_to?(:notify_start!)
|
32
|
+
Logger.puts "Notifying SystemdMon start via #{notifier.class}"
|
33
|
+
notifier.notify_start! hostname
|
34
|
+
else
|
35
|
+
Logger.debug { "#{notifier.class} doesn't respond to 'notify_start!', not sending notification" }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def notify_stop!(hostname)
|
41
|
+
each_notifier do |notifier|
|
42
|
+
if notifier.respond_to?(:notify_stop!)
|
43
|
+
Logger.puts "Notifying SystemdMon stop via #{notifier.class}"
|
44
|
+
notifier.notify_stop! hostname
|
45
|
+
else
|
46
|
+
Logger.debug { "#{notifier.class} doesn't respond to 'notify_start!', not sending notification" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def notify!(notification)
|
52
|
+
each_notifier do |notifier|
|
53
|
+
Logger.puts "Notifying state change of #{notification.unit.name} via #{notifier.class}"
|
54
|
+
notifier.notify! notification
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
alias :<< :add_notifier
|
59
|
+
|
60
|
+
protected
|
61
|
+
attr_accessor :notifiers
|
62
|
+
|
63
|
+
def each_notifier
|
64
|
+
notifiers.map { |notifier|
|
65
|
+
Thread.new do
|
66
|
+
begin
|
67
|
+
yield notifier
|
68
|
+
rescue => e
|
69
|
+
Logger.error "Failed to send notification via #{notifier.class}:\n"
|
70
|
+
Logger.error " #{e.class}: #{e.message}\n"
|
71
|
+
Logger.debug_error { "\n\t#{e.backtrace.join('\n\t')}\n" }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
}.each(&:join)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
class NotifierLoader
|
3
|
+
def get_class(name)
|
4
|
+
class_name = camel_case(name)
|
5
|
+
get_class_const(class_name)
|
6
|
+
rescue NameError
|
7
|
+
require "systemd_mon/notifiers/#{name}"
|
8
|
+
get_class_const(class_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def camel_case(name)
|
13
|
+
return name if name !~ /_/ && name =~ /[A-Z]+.*/
|
14
|
+
name.split('_').map { |e| e.capitalize }.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_class_const(name)
|
18
|
+
Notifiers.const_get(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'systemd_mon/logger'
|
2
|
+
|
3
|
+
module SystemdMon::Notifiers
|
4
|
+
class Base
|
5
|
+
def initialize(options)
|
6
|
+
self.options = options
|
7
|
+
self.me = self.class.name
|
8
|
+
end
|
9
|
+
|
10
|
+
# Subclasses must respond to a unit change
|
11
|
+
def notify!(notification)
|
12
|
+
raise "Notifier #{self.class} does not respond to notify!"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Subclasses can choose to do something when SystemdMon starts
|
16
|
+
# E.g. with
|
17
|
+
#
|
18
|
+
# def notify_start!(hostname)
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Subclasses can choose to do something when SystemdMon stops
|
22
|
+
# E.g. with
|
23
|
+
#
|
24
|
+
# def notify_stop!(hostname)
|
25
|
+
# end
|
26
|
+
|
27
|
+
def log(message)
|
28
|
+
SystemdMon::Logger.puts "#{me}: #{message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def debug(message = nil, &blk)
|
32
|
+
message = "#{me}: #{message}" if message
|
33
|
+
SystemdMon::Logger.debug message, &blk
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
attr_accessor :options, :me
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require 'systemd_mon/notifiers/base'
|
3
|
+
require 'systemd_mon/logger'
|
4
|
+
require 'systemd_mon/formatters/state_table_formatter'
|
5
|
+
|
6
|
+
module SystemdMon::Notifiers
|
7
|
+
class Email < Base
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
if options['smtp']
|
11
|
+
opts = options
|
12
|
+
Mail.defaults do
|
13
|
+
delivery_method :smtp, Hash[opts['smtp'].map { |h, k| [h.to_sym, k] }]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
validate_options!
|
18
|
+
end
|
19
|
+
|
20
|
+
def notify!(notification)
|
21
|
+
unit = notification.unit
|
22
|
+
subject = "#{unit.name} on #{notification.hostname}: #{unit.state_change.status_text}"
|
23
|
+
message = "Systemd unit #{unit.name} on #{notification.hostname} #{unit.state_change.status_text}: #{unit.state.active} (#{unit.state.sub})\n\n"
|
24
|
+
if unit.state_change.length > 1
|
25
|
+
message << SystemdMon::Formatters::StateTableFormatter.new(unit).as_text
|
26
|
+
end
|
27
|
+
message << "\nRegards, SystemdMon"
|
28
|
+
|
29
|
+
send_mail subject, message
|
30
|
+
|
31
|
+
log "sent email notification"
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
attr_accessor :options
|
36
|
+
|
37
|
+
def validate_options!
|
38
|
+
unless options.has_key?("to")
|
39
|
+
raise NotifierError, "The 'to' address must be set to use the email notifier"
|
40
|
+
end
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_mail(subject, message)
|
45
|
+
debug("Sending email to #{options['to']}:")
|
46
|
+
debug(%Q{ -> Subject: "#{subject}"})
|
47
|
+
debug(%Q{ -> Message: "#{message}"})
|
48
|
+
|
49
|
+
mail = Mail.new do
|
50
|
+
subject subject
|
51
|
+
body message
|
52
|
+
end
|
53
|
+
mail.to = options['to']
|
54
|
+
if options['from']
|
55
|
+
mail.from options['from']
|
56
|
+
end
|
57
|
+
mail.deliver!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'slack-notifier'
|
2
|
+
require 'systemd_mon/notifiers/base'
|
3
|
+
|
4
|
+
module SystemdMon::Notifiers
|
5
|
+
class Slack < Base
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
self.notifier = ::Slack::Notifier.new(
|
9
|
+
options.fetch('team'),
|
10
|
+
options.fetch('token'),
|
11
|
+
channel: options['channel'],
|
12
|
+
username: options['username'],
|
13
|
+
icon_emoji: options['icon_emoji'],
|
14
|
+
icon_url: options['icon_url'])
|
15
|
+
end
|
16
|
+
|
17
|
+
def notify_start!(hostname)
|
18
|
+
message = "Startup notification for SystemdMon"
|
19
|
+
|
20
|
+
attach = {
|
21
|
+
fallback: message,
|
22
|
+
text: "SystemdMon is starting on #{hostname}",
|
23
|
+
color: "good"
|
24
|
+
}
|
25
|
+
|
26
|
+
notifier.ping message, attachments: [attach]
|
27
|
+
end
|
28
|
+
|
29
|
+
def notify_stop!(hostname)
|
30
|
+
message = "Shutdown alert for SystemdMon"
|
31
|
+
|
32
|
+
attach = {
|
33
|
+
fallback: message,
|
34
|
+
text: "SystemdMon is stopping on #{hostname}",
|
35
|
+
color: "danger"
|
36
|
+
}
|
37
|
+
|
38
|
+
notifier.ping message, attachments: [attach]
|
39
|
+
end
|
40
|
+
|
41
|
+
def notify!(notification)
|
42
|
+
unit = notification.unit
|
43
|
+
message = "Systemd unit #{unit.name} on #{notification.hostname} #{unit.state_change.status_text}"
|
44
|
+
|
45
|
+
attach = {
|
46
|
+
fallback: "#{message}: #{unit.state.active} (#{unit.state.sub})",
|
47
|
+
color: color(notification.type),
|
48
|
+
fields: fields(notification)
|
49
|
+
}
|
50
|
+
|
51
|
+
debug("sending slack message with attachment: ")
|
52
|
+
debug(attach.inspect)
|
53
|
+
|
54
|
+
notifier.ping message, attachments: [attach]
|
55
|
+
log "sent slack notification"
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
attr_accessor :notifier
|
60
|
+
|
61
|
+
def fields(notification)
|
62
|
+
f = [
|
63
|
+
{
|
64
|
+
title: "Hostname",
|
65
|
+
value: notification.hostname,
|
66
|
+
short: true
|
67
|
+
},
|
68
|
+
{
|
69
|
+
title: "Unit",
|
70
|
+
value: notification.unit.name,
|
71
|
+
short: true
|
72
|
+
}
|
73
|
+
]
|
74
|
+
|
75
|
+
changes = notification.unit.state_change.diff.map(&:last)
|
76
|
+
f.concat(changes.map { |v|
|
77
|
+
{ title: v.display_name, value: v.value, short: true }
|
78
|
+
})
|
79
|
+
end
|
80
|
+
|
81
|
+
def color(type)
|
82
|
+
case type
|
83
|
+
when :alert
|
84
|
+
'danger'
|
85
|
+
when :info
|
86
|
+
'#0099CC'
|
87
|
+
else
|
88
|
+
'good'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'systemd_mon/state_value'
|
2
|
+
|
3
|
+
module SystemdMon
|
4
|
+
class State
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :active, :sub, :loaded, :unit_file, :all_states
|
8
|
+
|
9
|
+
def initialize(active, sub, loaded, unit_file)
|
10
|
+
timestamp = Time.now
|
11
|
+
@active = StateValue.new("active", active, timestamp, %w(active), %w(inactive))
|
12
|
+
@sub = StateValue.new("status", sub, timestamp)
|
13
|
+
@loaded = StateValue.new("loaded", loaded, timestamp, %w(loaded))
|
14
|
+
@unit_file = StateValue.new("file", unit_file, timestamp, %w(enabled), %w(disabled))
|
15
|
+
@all_states = [@active, @sub, @loaded, @unit_file]
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
@all_states.each do |state|
|
20
|
+
yield state
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ok?
|
25
|
+
all?(&:ok?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fail?
|
29
|
+
any?(&:fail?)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
class StateChange
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :states
|
6
|
+
|
7
|
+
def initialize(original_state = nil)
|
8
|
+
self.states = []
|
9
|
+
states << original_state if original_state
|
10
|
+
end
|
11
|
+
|
12
|
+
def last
|
13
|
+
states.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def length
|
17
|
+
states.length
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(state)
|
21
|
+
self.states << state
|
22
|
+
@diff = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def each
|
26
|
+
states.each do |state|
|
27
|
+
yield state
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def changes
|
32
|
+
states[1..-1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def recovery?
|
36
|
+
first.fail? && last.ok?
|
37
|
+
end
|
38
|
+
|
39
|
+
def ok?
|
40
|
+
last.ok?
|
41
|
+
end
|
42
|
+
|
43
|
+
def fail?
|
44
|
+
last.fail?
|
45
|
+
end
|
46
|
+
|
47
|
+
def restart?
|
48
|
+
first.ok? && last.ok? && changes.any? { |s| s.active == "deactivating" }
|
49
|
+
end
|
50
|
+
|
51
|
+
def reload?
|
52
|
+
first.ok? && last.ok? && changes.any? { |s| s.active == "reloading" }
|
53
|
+
end
|
54
|
+
|
55
|
+
def still_fail?
|
56
|
+
length > 1 && first.fail? && last.fail?
|
57
|
+
end
|
58
|
+
|
59
|
+
def status_text
|
60
|
+
if recovery?
|
61
|
+
"recovered"
|
62
|
+
elsif restart?
|
63
|
+
"restarted"
|
64
|
+
elsif reload?
|
65
|
+
"reloaded"
|
66
|
+
elsif still_fail?
|
67
|
+
"still failed"
|
68
|
+
elsif fail?
|
69
|
+
"failed"
|
70
|
+
else
|
71
|
+
"started"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def important?
|
76
|
+
if length == 1
|
77
|
+
first.fail?
|
78
|
+
else
|
79
|
+
diff.map(&:last).any?(&:important?)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def diff
|
84
|
+
@diff ||= zipped.reject { |states|
|
85
|
+
match = states.first.value
|
86
|
+
states.all? { |s| s.value == match }
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def zipped
|
91
|
+
if length == 1
|
92
|
+
first.all_states
|
93
|
+
else
|
94
|
+
first.all_states.zip(*changes.map(&:all_states))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
diff.inject("") { |s, (*states)|
|
100
|
+
first = states.shift
|
101
|
+
s << "#{first.name} state changed from #{first.value} to "
|
102
|
+
s << states.map(&:value).join(" then ")
|
103
|
+
s << "\n"
|
104
|
+
s
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
attr_accessor :original, :changed
|
110
|
+
attr_writer :states
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module SystemdMon
|
2
|
+
class StateValue
|
3
|
+
attr_reader :name, :value, :ok_states, :failure_states, :timestamp
|
4
|
+
|
5
|
+
def initialize(name, value, timestamp, ok_states = [], failure_states = [])
|
6
|
+
self.name = name
|
7
|
+
self.value = value
|
8
|
+
self.ok_states = ok_states
|
9
|
+
self.failure_states = failure_states
|
10
|
+
self.timestamp = timestamp
|
11
|
+
end
|
12
|
+
|
13
|
+
def display_name
|
14
|
+
name.capitalize
|
15
|
+
end
|
16
|
+
|
17
|
+
def important?
|
18
|
+
ok_states.include?(value) || failure_states.include?(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ok?
|
22
|
+
if ok_states.any?
|
23
|
+
ok_states.include?(value)
|
24
|
+
else
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def fail?
|
30
|
+
if failure_states.any?
|
31
|
+
failure_states.include?(value)
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
value == other
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
attr_writer :name, :value, :ok_states, :failure_states, :timestamp
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'systemd_mon/state_change'
|
2
|
+
|
3
|
+
module SystemdMon
|
4
|
+
class UnitWithState
|
5
|
+
attr_reader :unit, :state_change
|
6
|
+
|
7
|
+
def initialize(unit)
|
8
|
+
self.unit = unit
|
9
|
+
self.state_change = StateChange.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
unit.name
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(state)
|
17
|
+
self.state_change << state
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_state
|
21
|
+
state_change.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset!
|
25
|
+
self.state_change = StateChange.new(current_state)
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :state :current_state
|
29
|
+
|
30
|
+
protected
|
31
|
+
attr_writer :state_change, :unit
|
32
|
+
end
|
33
|
+
end
|
data/systemd_mon.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'systemd_mon/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "systemd_mon"
|
8
|
+
spec.version = SystemdMon::VERSION
|
9
|
+
spec.authors = ["Jon Cairns"]
|
10
|
+
spec.email = ["jon@joncairns.com"]
|
11
|
+
spec.summary = %q{Monitor systemd units and trigger alerts for failed states}
|
12
|
+
spec.description = %q{Monitor systemd units and trigger alerts for failed states}
|
13
|
+
spec.homepage = "https://github.com/joonty/systemd_mon"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "ruby-dbus"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: systemd_mon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jon Cairns
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-dbus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Monitor systemd units and trigger alerts for failed states
|
56
|
+
email:
|
57
|
+
- jon@joncairns.com
|
58
|
+
executables:
|
59
|
+
- systemd_mon
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/systemd_mon
|
69
|
+
- lib/systemd_mon.rb
|
70
|
+
- lib/systemd_mon/callback_manager.rb
|
71
|
+
- lib/systemd_mon/cli.rb
|
72
|
+
- lib/systemd_mon/dbus_manager.rb
|
73
|
+
- lib/systemd_mon/dbus_unit.rb
|
74
|
+
- lib/systemd_mon/error.rb
|
75
|
+
- lib/systemd_mon/formatters/base.rb
|
76
|
+
- lib/systemd_mon/formatters/state_table_formatter.rb
|
77
|
+
- lib/systemd_mon/logger.rb
|
78
|
+
- lib/systemd_mon/monitor.rb
|
79
|
+
- lib/systemd_mon/notification.rb
|
80
|
+
- lib/systemd_mon/notification_centre.rb
|
81
|
+
- lib/systemd_mon/notifier_loader.rb
|
82
|
+
- lib/systemd_mon/notifiers/base.rb
|
83
|
+
- lib/systemd_mon/notifiers/email.rb
|
84
|
+
- lib/systemd_mon/notifiers/slack.rb
|
85
|
+
- lib/systemd_mon/state.rb
|
86
|
+
- lib/systemd_mon/state_change.rb
|
87
|
+
- lib/systemd_mon/state_value.rb
|
88
|
+
- lib/systemd_mon/unit_with_state.rb
|
89
|
+
- lib/systemd_mon/version.rb
|
90
|
+
- systemd_mon.gemspec
|
91
|
+
homepage: https://github.com/joonty/systemd_mon
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.2.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Monitor systemd units and trigger alerts for failed states
|
115
|
+
test_files: []
|