1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
__author__ = 'Kenny Freeman' |
4
|
|
|
__email__ = '[email protected]' |
5
|
|
|
__license__ = "ISCL" |
6
|
|
|
__docformat__ = 'reStructuredText' |
7
|
|
|
|
8
|
|
|
import os |
9
|
|
|
import sys |
10
|
|
|
import time |
11
|
|
|
import signal |
12
|
|
|
import logging |
13
|
|
|
import argparse |
14
|
|
|
import threading |
15
|
|
|
|
16
|
|
|
import structlog # pip install structlog |
17
|
|
|
|
18
|
|
|
import plumd |
19
|
|
|
import plumd.util |
20
|
|
|
import plumd.config |
21
|
|
|
import plumd.plugins.load |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
def get_config(): |
25
|
|
|
"""Returns a conf object - either from the default file or cmd line arg. |
26
|
|
|
|
27
|
|
|
:rtype: plumd.config.conf |
28
|
|
|
""" |
29
|
|
|
descr = """plumd: measures system and service metrics.""" |
30
|
|
|
# get cli options - currently just the path to this instances config file |
31
|
|
|
argp = argparse.ArgumentParser(description=descr) |
32
|
|
|
argp.add_argument('--config', metavar='config', type=str, |
33
|
|
|
nargs='?', default=plumd.DEFAULT_CONFIG['config.file'], |
34
|
|
|
help='main configuration file in yaml format') |
35
|
|
|
args = argp.parse_args() |
36
|
|
|
|
37
|
|
|
# get a simple configuration helper object |
38
|
|
|
config = None |
39
|
|
|
try: |
40
|
|
|
config = plumd.config.Conf(args.config).defaults(plumd.DEFAULT_CONFIG) |
41
|
|
|
except plumd.ConfigError as e: |
42
|
|
|
sys.stderr.write("configuration failed to load: {0}\n".format(e)) |
43
|
|
|
sys.exit(1) |
44
|
|
|
|
45
|
|
|
# ensure log level is known |
46
|
|
|
llevel = config.get('log.level') |
47
|
|
|
if llevel not in plumd.LOG_LEVELS: |
48
|
|
|
llevels =" ".join(plumd.LOG_LEVELS.keys()) |
49
|
|
|
sys.stderr.write("unkonwn log level: {0}\n".format(llevel)) |
50
|
|
|
sys.stderr.write("valid log levels are: {0}\n".format(llevels)) |
51
|
|
|
sys.exit(1) |
52
|
|
|
|
53
|
|
|
# all done, return our config |
54
|
|
|
return config |
55
|
|
|
|
56
|
|
|
|
57
|
|
|
def initialize(config): |
58
|
|
|
"""Do various initilization things. |
59
|
|
|
|
60
|
|
|
:param config: a :class:`plumd.config.conf` instance |
61
|
|
|
:type config: plumd.config.conf |
62
|
|
|
""" |
63
|
|
|
# set the configured timezone, from: |
64
|
|
|
# http://stackoverflow.com/questions/6321160/python-logging-how-to-set-time-to-gmt |
65
|
|
|
tz = config.get('tz') |
66
|
|
|
if tz: |
67
|
|
|
try: |
68
|
|
|
os.environ['TZ'] = tz |
69
|
|
|
time.tzset() |
70
|
|
|
except Exception as e: |
71
|
|
|
err = "unable to set timezone: {0} : {1}\n" |
72
|
|
|
sys.stderr.write(err.format(tz, e)) |
73
|
|
|
sys.exit(1) |
74
|
|
|
|
75
|
|
|
# add hostname metadata |
76
|
|
|
if config.get('meta.hostname'): |
77
|
|
|
meta = config.get('meta') |
78
|
|
|
if 'hostname' not in meta: |
79
|
|
|
meta['hostname'] = plumd.util.get_hostname() |
80
|
|
|
config.set_conf('meta', meta) |
81
|
|
|
else: |
82
|
|
|
err = "meta.hostname: hostname already set in config: {0} : {1}\n" |
83
|
|
|
sys.stderr.write(err.format(meta['hostname'], config.path)) |
84
|
|
|
sys.exit(1) |
85
|
|
|
|
86
|
|
|
return config |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
def get_log(config, src): |
90
|
|
|
"""Returns a configured logger. |
91
|
|
|
|
92
|
|
|
:param config: a :class:`plumd.config.conf` instance |
93
|
|
|
:type config: plumd.config.conf |
94
|
|
|
:param src: all log entries generated are tagged with src=<src> |
95
|
|
|
:type src: str |
96
|
|
|
|
97
|
|
|
:rtype: structlog.logger |
98
|
|
|
""" |
99
|
|
|
# setup logging, use structlog |
100
|
|
|
handler = logging.StreamHandler(sys.stdout) |
101
|
|
|
stdlog = logging.getLogger() |
102
|
|
|
stdlog.addHandler(handler) |
103
|
|
|
stdlog.setLevel(plumd.LOG_LEVELS[config.get('log.level')]) |
104
|
|
|
order=['timestamp', 'src', 'level', 'event'] |
105
|
|
|
structlog.configure( |
106
|
|
|
processors=[ |
107
|
|
|
structlog.stdlib.filter_by_level, |
108
|
|
|
structlog.stdlib.add_log_level, |
109
|
|
|
structlog.processors.TimeStamper(fmt="iso"), |
110
|
|
|
structlog.processors.KeyValueRenderer(sort_keys=False, |
111
|
|
|
key_order=order) |
112
|
|
|
], |
113
|
|
|
context_class=dict, |
114
|
|
|
logger_factory=structlog.stdlib.LoggerFactory(), |
115
|
|
|
wrapper_class=structlog.stdlib.BoundLogger, |
116
|
|
|
cache_logger_on_first_use=True, |
117
|
|
|
) |
118
|
|
|
return structlog.get_logger().bind(src=src) |
119
|
|
|
|
120
|
|
|
|
121
|
|
|
def get_plugins(config, log): |
122
|
|
|
"""Returns a plugin loader object with plugins already started. |
123
|
|
|
|
124
|
|
|
:param config: a :class:`plumd.config.conf` instance |
125
|
|
|
:type config: plumd.config.conf |
126
|
|
|
:param log: a structlog logging instance |
127
|
|
|
:type log: structlog |
128
|
|
|
|
129
|
|
|
:rtype: plumd.load.PluginLoader""" |
130
|
|
|
# load reader and writer plugins and start |
131
|
|
|
log.info("starting", component="plugins") |
132
|
|
|
psyslog = structlog.get_logger().bind(src="PluginLoader") |
133
|
|
|
psys = None |
134
|
|
|
# several things can go wrong loading plugins |
135
|
|
|
try: |
136
|
|
|
psys = plumd.plugins.load.PluginLoader(psyslog, config) |
137
|
|
|
except plumd.PluginLoadError as e: |
138
|
|
|
log.critical("plugin load failed", exception=e) |
139
|
|
|
sys.exit(1) |
140
|
|
|
except plumd.ConfigError as e: |
141
|
|
|
log.critical("invalid plugin config", exception=e) |
142
|
|
|
sys.exit(1) |
143
|
|
|
except plumd.DuplicatePlugin as e: |
144
|
|
|
log.critical("duplicate plugin", exception=e) |
145
|
|
|
sys.exit(1) |
146
|
|
|
else: |
147
|
|
|
time.sleep(config.get('delay.startup')) |
148
|
|
|
psys.start() |
149
|
|
|
|
150
|
|
|
# all done, return the PluginLoader object |
151
|
|
|
return psys |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
def main(): |
155
|
|
|
"""Main entry point for plumd.""" |
156
|
|
|
|
157
|
|
|
# setup signal handling |
158
|
|
|
sig_wait = [ signal.SIGINT, signal.SIGTERM, signal.SIGQUIT ] |
159
|
|
|
sig_ignore = [ signal.SIGUSR1, signal.SIGUSR2, signal.SIGHUP ] |
160
|
|
|
swait = plumd.util.SignalWaiter(sig_wait=sig_wait, sig_ignore=sig_ignore) |
161
|
|
|
|
162
|
|
|
# get a configuration helper object |
163
|
|
|
config = get_config() |
164
|
|
|
|
165
|
|
|
# initialize it |
166
|
|
|
config = initialize(config) |
167
|
|
|
|
168
|
|
|
# create a logger |
169
|
|
|
log = get_log(config=config, src="main") |
170
|
|
|
log.info("starting") |
171
|
|
|
|
172
|
|
|
# get the plugin loader with all plugins started |
173
|
|
|
psys = get_plugins(log=log, config=config) |
174
|
|
|
|
175
|
|
|
# check to ensure we've loaded a sane configuration |
176
|
|
|
# is at least one reader/writer plugin loaded? |
177
|
|
|
if psys.nplugins < 1: |
178
|
|
|
log.error("exiting", error="no plugins loaded") |
179
|
|
|
sys.exit(1) |
180
|
|
|
# writer plugin? |
181
|
|
|
elif psys.nwriters < 1: |
182
|
|
|
log.error("exiting", error="no writers loaded") |
183
|
|
|
sys.exit(1) |
184
|
|
|
# reader plugin? |
185
|
|
|
elif psys.nreaders < 1: |
186
|
|
|
log.error("exiting", error="no readers loaded") |
187
|
|
|
sys.exit(1) |
188
|
|
|
|
189
|
|
|
# all set, now wait for the exit signal |
190
|
|
|
log.info("running") |
191
|
|
|
swait.wait() |
192
|
|
|
|
193
|
|
|
# stop readers/writers/bridge |
194
|
|
|
log.info("stopping") |
195
|
|
|
psys.stop() |
196
|
|
|
|
197
|
|
|
# all done, exit |
198
|
|
|
log.info("exiting") |
199
|
|
|
sys.exit(0) |
200
|
|
|
|
201
|
|
|
|
202
|
|
|
if __name__ == "__main__": |
203
|
|
|
main() |
204
|
|
|
|