From eb149b9129a5e08bb2127f8bd2c17161f4a4ee3e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 22 Jun 2015 12:41:08 +0000 Subject: [PATCH] CLI debug log file --- letsencrypt/cli.py | 45 ++++++++++++++++++++++++++++++----- letsencrypt/constants.py | 1 + letsencrypt/renewer.py | 3 +++ letsencrypt/tests/cli_test.py | 18 +++++++++++--- tests/boulder-integration.sh | 1 + 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 2a9ec8405..08d594ab1 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -3,8 +3,10 @@ import argparse import atexit import logging +import logging.handlers import os import sys +import time import configargparse import zope.component @@ -70,9 +72,6 @@ More detailed help: def _account_init(args, config): - le_util.make_or_verify_dir( - config.config_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) - # Prepare for init of Client if args.email is None: return client.determine_account(config) @@ -559,6 +558,8 @@ def _paths_parser(helpful): help=config_help("config_dir")) add("paths", "--work-dir", default=flag_default("work_dir"), help=config_help("work_dir")) + add("paths", "--logs-dir", default=flag_default("logs_dir"), + help="Path to a directory where logs are stored.") add("paths", "--backup-dir", default=flag_default("backup_dir"), help=config_help("backup_dir")) add("paths", "--key-dir", default=flag_default("key_dir"), @@ -575,14 +576,39 @@ def _paths_parser(helpful): def _setup_logging(args): level = -args.verbose_count * 10 - logging.getLogger().setLevel(level) + fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" if args.text_mode: handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(fmt)) else: handler = log.DialogHandler() - handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) - logging.getLogger().addHandler(handler) + # dialog box is small, display as less as possible + handler.setFormatter(logging.Formatter("%(message)s")) + handler.setLevel(level) + + # TODO: use fileConfig? + + # unconditionally log to file for debugging purposes + # TODO: change before release? + log_file_name = os.path.join(args.logs_dir, 'letsencrypt.log') + file_handler = logging.handlers.RotatingFileHandler( + log_file_name, maxBytes=2**20, backupCount=10) + # rotate on each invocation, rollover only possible when maxBytes + # is nonzero and backupCount is nonzero, so we set maxBytes as big + # as possible not to overrun in single CLI invocation (1MB). + file_handler.doRollover() # TODO: creates empty letsencrypt.log.1 file + file_handler.setLevel(logging.DEBUG) + file_handler_formatter = logging.Formatter(fmt=fmt) + file_handler_formatter.converter = time.gmtime # don't use localtime + file_handler.setFormatter(file_handler_formatter) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) # send all records to handlers + root_logger.addHandler(handler) + root_logger.addHandler(file_handler) + logging.debug("Root logging level set at %d", level) + logging.info("Saving debug log to %s", log_file_name) def main(cli_args=sys.argv[1:]): @@ -599,6 +625,13 @@ def main(cli_args=sys.argv[1:]): displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) + for directory in config.config_dir, config.work_dir: + le_util.make_or_verify_dir( + directory, constants.CONFIG_DIRS_MODE, os.geteuid()) + # TODO: logs might contain sensitive data such as contents of the + # private key! #525 + le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) + _setup_logging(args) # do not log `args`, as it contains sensitive data (e.g. revoke --key)! logging.debug("Arguments: %r", cli_args) diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 81ec4a90b..46d9aa044 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -15,6 +15,7 @@ CLI_DEFAULTS = dict( rollback_checkpoints=0, config_dir="/etc/letsencrypt", work_dir="/var/lib/letsencrypt", + logs_dir="/var/log/letsencrypt", no_verify_ssl=False, dvsni_port=challenges.DVSNI.PORT, cert_path="./cert.pem", diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index d2c0b8e7d..47fbefcf8 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -106,6 +106,9 @@ def _paths_parser(parser): help=cli.config_help("config_dir")) add("--work-dir", default=cli.flag_default("work_dir"), help=cli.config_help("work_dir")) + add("--logs-dir", default=cli.flag_default("logs_dir"), + help="Path to a directory where logs are stored.") + return parser diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 9bb8084d5..1e8e84f6d 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -1,5 +1,8 @@ """Tests for letsencrypt.cli.""" import itertools +import os +import shutil +import tempfile import unittest import mock @@ -8,10 +11,19 @@ import mock class CLITest(unittest.TestCase): """Tests for different commands.""" - @classmethod - def _call(cls, args): + def setUp(self): + self.tmp_dir = tempfile.mkdtemp() + self.config_dir = os.path.join(self.tmp_dir, 'config') + self.work_dir = os.path.join(self.tmp_dir, 'work') + self.logs_dir = os.path.join(self.tmp_dir, 'logs') + + def tearDown(self): + shutil.rmtree(self.tmp_dir) + + def _call(self, args): from letsencrypt import cli - args = ['--text'] + args + args = ['--text', '--config-dir', self.config_dir, + '--work-dir', self.work_dir, '--logs-dir', self.logs_dir] + args with mock.patch('letsencrypt.cli.sys.stdout') as stdout: with mock.patch('letsencrypt.cli.sys.stderr') as stderr: with mock.patch('letsencrypt.cli.client') as client: diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index c3cc49c70..32255039b 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -6,6 +6,7 @@ root="$(mktemp -d)" echo "\nRoot integration tests directory: $root" store_flags="--config-dir $root/conf --work-dir $root/work" +store_flags="$store_flags --logs-dir $root/logs" common() { # first three flags required, rest is handy defaults