| #!/usr/bin/env python | |
| # loggly_eb.py | |
| ''' | |
| For configuring loggly on AWD elastic beanstalk instances. | |
| Tested on the following EB configurations: | |
| 64 bit Amazon Linux 2017.03 v.2.5.2 running Python 3.4 | |
| This is a RHEL-like linux, with rsyslog and no journald. | |
| No checks are made at runtime to test the validity of the account/token or to verify | |
| log delivery. Use the regular 'configure-linux.sh' script interactively for that. | |
| Settings are taken from environment variables, overridable with command line arguments: | |
| - LOGGLY_ACCOUNT - the account name at Loggly | |
| - LOGGLY_TOKEN - the loggly authentication token (a UUID) for submitting data | |
| - LOGGLY_TAGS - a space-separated string of additional tags to apply to the log | |
| - LOGGLY_HOSTNAME - the hostname to report in syslog. If not specified, the default | |
| as reported by gethostname() is used. | |
| - LOGGLY_DOMAIN - domain name appended to hostname. Useful to name a cluster of | |
| virtual hosts. | |
| ''' | |
| from __future__ import print_function | |
| import os | |
| import os.path | |
| import sys | |
| import argparse | |
| import subprocess | |
| import shlex | |
| import textwrap | |
| import shutil | |
| import socket | |
| import six | |
| LOGGLY_DISTRIBUTION_ID = '41058' | |
| LOGS_01_HOST = 'logs-01.loggly.com' | |
| LOGGLY_SYSLOG_PORT = 514 | |
| # directory location for syslog | |
| RSYSLOG_ETCDIR_CONF = '/etc/rsyslog.d' | |
| # name and location of loggly syslog file | |
| LOGGLY_RSYSLOG_CONFFILE = RSYSLOG_ETCDIR_CONF + '/22-loggly.conf' | |
| # name and location of loggly syslog backup file | |
| LOGGLY_RSYSLOG_CONFFILE_BACKUP = LOGGLY_RSYSLOG_CONFFILE + '.loggly.bk' | |
| # rsyslog service name | |
| RSYSLOG_SERVICE = 'rsyslog' | |
| def main(): | |
| # parse args | |
| parser = argparse.ArgumentParser(description='Initialize loggly logging') | |
| parser.add_argument('--account', '-a', default=os.environ.get('LOGGLY_ACCOUNT'), help='The Loggly account name (default from LOGGLY_ACCOUNT)') | |
| parser.add_argument('--token', '-t', default=os.environ.get('LOGGLY_TOKEN'), help='The Loggly authentication token (default from LOGGLY_TOKEN)') | |
| parser.add_argument('--tags', nargs='*', help='Additional tags (default from LOGGLY_TAGS)') | |
| parser.add_argument('--hostname', default=os.environ.get('LOGGLY_HOSTNAME')) | |
| parser.add_argument('--domain', default=os.environ.get('LOGGLY_DOMAIN')) | |
| parser.add_argument('--remove', '-r', action='store_true', help='remove loggly configuration') | |
| args = parser.parse_args() | |
| if args.remove: | |
| unconfigure() | |
| return | |
| if not args.account or not args.token: | |
| # No loggly configuration | |
| print("No Loggly credentials. '%s -h' for help" % parser.prog) | |
| unconfigure(quiet=True) | |
| return | |
| if args.tags is not None: | |
| tags = args.tags | |
| elif os.environ.get('LOGGLY_TAGS'): | |
| tags = shlex.split(os.environ['LOGGLY_TAGS']) | |
| else: | |
| tags = None | |
| config = { | |
| 'account': args.account, | |
| 'token': args.token, | |
| 'tags': tags, | |
| 'hostname': args.hostname, | |
| 'domain' : args.domain, | |
| } | |
| configure(config) | |
| def configure(config): | |
| log("INFO: Initiating Configure Loggly for Linux.") | |
| # if all the above check passes, write the 22-loggly.conf file | |
| write_rsyslog_conf(config) | |
| # restart rsyslog service | |
| restart_rsyslog() | |
| log("SUCCESS: Linux system successfully configured to send logs via Loggly.") | |
| def write_rsyslog_conf(config): | |
| '''write the contents to 22-loggly.conf file''' | |
| contents = get_loggly_conf(config) | |
| write_script = False | |
| if os.path.exists(LOGGLY_RSYSLOG_CONFFILE): | |
| log("INFO: Loggly rsyslog file %r already exist." % LOGGLY_RSYSLOG_CONFFILE) | |
| with open(LOGGLY_RSYSLOG_CONFFILE, "rb") as fd: | |
| old_contents = fd.read() | |
| if six.b(contents) != old_contents: | |
| log("WARN: Loggly rsyslog file %r content has changed." % LOGGLY_RSYSLOG_CONFFILE) | |
| log("INFO: Going to back up the conf file: %r to %r" % (LOGGLY_RSYSLOG_CONFFILE, LOGGLY_RSYSLOG_CONFFILE_BACKUP)) | |
| shutil.move(LOGGLY_RSYSLOG_CONFFILE, LOGGLY_RSYSLOG_CONFFILE_BACKUP) | |
| write_script = True | |
| else: | |
| write_script = True | |
| if write_script: | |
| with open(LOGGLY_RSYSLOG_CONFFILE, "wb") as fd: | |
| fd.write(six.b(contents)) | |
| log("INFO: Loggly rsyslog file %r written." % LOGGLY_RSYSLOG_CONFFILE) | |
| def unconfigure(quiet=False): | |
| # in quiet mode, don't log anything if not configured | |
| if quiet and not os.path.exists(LOGGLY_RSYSLOG_CONFFILE): | |
| return | |
| log("INFO: Initiating uninstall Loggly for Linux.") | |
| # remove 22-loggly.conf file | |
| remove_loggly_conf() | |
| # restart rsyslog service | |
| restart_rsyslog() | |
| log("SUCCESS: Uninstalled Loggly configuration from Linux system.") | |
| def remove_loggly_conf(): | |
| '''delete 22-loggly.conf file''' | |
| if os.path.exists(LOGGLY_RSYSLOG_CONFFILE): | |
| os.unlink(LOGGLY_RSYSLOG_CONFFILE) | |
| return True | |
| def restart_rsyslog(): | |
| '''restart rsyslog''' | |
| log("INFO: Restarting the %s service." % RSYSLOG_SERVICE) | |
| code = subprocess.call(['service', RSYSLOG_SERVICE, 'restart']) | |
| if code: | |
| log("WARNING: %s did not restart gracefully. Please restart %s manually." % (RSYSLOG_SERVICE, RSYSLOG_SERVICE)) | |
| def log(msg): | |
| print(msg) | |
| if msg.startswith('ERROR'): | |
| sys.exit(1) | |
| def get_loggly_conf(config): | |
| template = r''' | |
| # ------------------------------------------------------- | |
| # Syslog Logging Directives for Loggly (%(account)s.loggly.com) | |
| # ------------------------------------------------------- | |
| # Define the template used for sending logs to Loggly. Do not change this format. | |
| $template LogglyFormat,"<%%pri%%>%%protocol-version%% %%timestamp:::date-rfc3339%% %(hostname)s %%app-name%% %%procid%% %%msgid%% [%(token)s@%(distribution_id)s %(tags)s] %%msg%%\n" | |
| $WorkDirectory /var/spool/rsyslog # where to place spool files | |
| $ActionQueueFileName fwdRule1 # unique name prefix for spool files | |
| $ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) | |
| $ActionQueueSaveOnShutdown on # save messages to disk on shutdown | |
| $ActionQueueType LinkedList # run asynchronously | |
| $ActionResumeRetryCount -1 # infinite retries if host is down | |
| # Send messages to Loggly over TCP using the template. | |
| *.* @@%(logs_host)s:%(logs_port)d;LogglyFormat | |
| # ------------------------------------------------------- | |
| ''' | |
| # preprocess data | |
| args = dict(config) | |
| if not args.get('hostname') and not args.get('domain'): | |
| # simply use the rsyslog macro | |
| args['hostname'] = '%HOSTNAME%' | |
| else: | |
| # construct a fixed hostname to send | |
| if not args.get('hostname'): | |
| args['hostname'] = socket.gethostname() | |
| if args.get('domain'): | |
| args['hostname'] = args['hostname'].split('.')[0] + '.' + args['domain'] | |
| tags = ['Rsyslog'] | |
| if args.get('tags'): | |
| tags += args['tags'] | |
| args['tags'] = ' '.join((r'tag=\"%s\"' % tag) for tag in tags) | |
| args['distribution_id'] = LOGGLY_DISTRIBUTION_ID | |
| args['logs_host'] = LOGS_01_HOST | |
| args['logs_port'] = LOGGLY_SYSLOG_PORT | |
| return textwrap.dedent(template[1:]) % args | |
| if __name__ == '__main__': | |
| main() |