systemd_mon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|