Completed
Push — master ( 8d751b...7e5eed )
by Kenny
58s
created

plumd.signal_handler()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 3
rs 10
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