| Viewing file:  logs.py (5.57 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# Copyright (C) 2017 Canonical Ltd.#
 # This file is part of cloud-init. See LICENSE file for license information.
 
 """Define 'collect-logs' utility and handler to include in cloud-init cmd."""
 
 import argparse
 from datetime import datetime
 import os
 import shutil
 import sys
 
 from cloudinit.sources import INSTANCE_JSON_SENSITIVE_FILE
 from cloudinit.temp_utils import tempdir
 from cloudinit.subp import (ProcessExecutionError, subp)
 from cloudinit.util import (chdir, copy, ensure_dir, write_file)
 
 
 CLOUDINIT_LOGS = ['/var/log/cloud-init.log', '/var/log/cloud-init-output.log']
 CLOUDINIT_RUN_DIR = '/run/cloud-init'
 USER_DATA_FILE = '/var/lib/cloud/instance/user-data.txt'  # Optional
 
 
 def get_parser(parser=None):
 """Build or extend and arg parser for collect-logs utility.
 
 @param parser: Optional existing ArgumentParser instance representing the
 collect-logs subcommand which will be extended to support the args of
 this utility.
 
 @returns: ArgumentParser with proper argument configuration.
 """
 if not parser:
 parser = argparse.ArgumentParser(
 prog='collect-logs',
 description='Collect and tar all cloud-init debug info')
 parser.add_argument('--verbose', '-v', action='count', default=0,
 dest='verbosity', help="Be more verbose.")
 parser.add_argument(
 "--tarfile", '-t', default='cloud-init.tar.gz',
 help=('The tarfile to create containing all collected logs.'
 ' Default: cloud-init.tar.gz'))
 parser.add_argument(
 "--include-userdata", '-u', default=False, action='store_true',
 dest='userdata', help=(
 'Optionally include user-data from {0} which could contain'
 ' sensitive information.'.format(USER_DATA_FILE)))
 return parser
 
 
 def _copytree_ignore_sensitive_files(curdir, files):
 """Return a list of files to ignore if we are non-root"""
 if os.getuid() == 0:
 return ()
 return (INSTANCE_JSON_SENSITIVE_FILE,)  # Ignore root-permissioned files
 
 
 def _write_command_output_to_file(cmd, filename, msg, verbosity):
 """Helper which runs a command and writes output or error to filename."""
 try:
 out, _ = subp(cmd)
 except ProcessExecutionError as e:
 write_file(filename, str(e))
 _debug("collecting %s failed.\n" % msg, 1, verbosity)
 else:
 write_file(filename, out)
 _debug("collected %s\n" % msg, 1, verbosity)
 return out
 
 
 def _debug(msg, level, verbosity):
 if level <= verbosity:
 sys.stderr.write(msg)
 
 
 def _collect_file(path, out_dir, verbosity):
 if os.path.isfile(path):
 copy(path, out_dir)
 _debug("collected file: %s\n" % path, 1, verbosity)
 else:
 _debug("file %s did not exist\n" % path, 2, verbosity)
 
 
 def collect_logs(tarfile, include_userdata, verbosity=0):
 """Collect all cloud-init logs and tar them up into the provided tarfile.
 
 @param tarfile: The path of the tar-gzipped file to create.
 @param include_userdata: Boolean, true means include user-data.
 """
 if include_userdata and os.getuid() != 0:
 sys.stderr.write(
 "To include userdata, root user is required."
 " Try sudo cloud-init collect-logs\n")
 return 1
 tarfile = os.path.abspath(tarfile)
 date = datetime.utcnow().date().strftime('%Y-%m-%d')
 log_dir = 'cloud-init-logs-{0}'.format(date)
 with tempdir(dir='/tmp') as tmp_dir:
 log_dir = os.path.join(tmp_dir, log_dir)
 version = _write_command_output_to_file(
 ['cloud-init', '--version'],
 os.path.join(log_dir, 'version'),
 "cloud-init --version", verbosity)
 dpkg_ver = _write_command_output_to_file(
 ['dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'],
 os.path.join(log_dir, 'dpkg-version'),
 "dpkg version", verbosity)
 if not version:
 version = dpkg_ver if dpkg_ver else "not-available"
 _debug("collected cloud-init version: %s\n" % version, 1, verbosity)
 _write_command_output_to_file(
 ['dmesg'], os.path.join(log_dir, 'dmesg.txt'),
 "dmesg output", verbosity)
 _write_command_output_to_file(
 ['journalctl', '--boot=0', '-o', 'short-precise'],
 os.path.join(log_dir, 'journal.txt'),
 "systemd journal of current boot", verbosity)
 
 for log in CLOUDINIT_LOGS:
 _collect_file(log, log_dir, verbosity)
 if include_userdata:
 _collect_file(USER_DATA_FILE, log_dir, verbosity)
 run_dir = os.path.join(log_dir, 'run')
 ensure_dir(run_dir)
 if os.path.exists(CLOUDINIT_RUN_DIR):
 shutil.copytree(CLOUDINIT_RUN_DIR,
 os.path.join(run_dir, 'cloud-init'),
 ignore=_copytree_ignore_sensitive_files)
 _debug("collected dir %s\n" % CLOUDINIT_RUN_DIR, 1, verbosity)
 else:
 _debug("directory '%s' did not exist\n" % CLOUDINIT_RUN_DIR, 1,
 verbosity)
 with chdir(tmp_dir):
 subp(['tar', 'czvf', tarfile, log_dir.replace(tmp_dir + '/', '')])
 sys.stderr.write("Wrote %s\n" % tarfile)
 return 0
 
 
 def handle_collect_logs_args(name, args):
 """Handle calls to 'cloud-init collect-logs' as a subcommand."""
 return collect_logs(args.tarfile, args.userdata, args.verbosity)
 
 
 def main():
 """Tool to collect and tar all cloud-init related logs."""
 parser = get_parser()
 return handle_collect_logs_args('collect-logs', parser.parse_args())
 
 
 if __name__ == '__main__':
 sys.exit(main())
 
 # vi: ts=4 expandtab
 
 |