aboutsummaryrefslogtreecommitdiffstats
path: root/python/mozlog/mozlog/structured/commandline.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozlog/mozlog/structured/commandline.py')
-rw-r--r--python/mozlog/mozlog/structured/commandline.py225
1 files changed, 225 insertions, 0 deletions
diff --git a/python/mozlog/mozlog/structured/commandline.py b/python/mozlog/mozlog/structured/commandline.py
new file mode 100644
index 00000000000..d4a993febe5
--- /dev/null
+++ b/python/mozlog/mozlog/structured/commandline.py
@@ -0,0 +1,225 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+import os
+import optparse
+
+from collections import defaultdict
+from structuredlog import StructuredLogger, set_default_logger
+import handlers
+import formatters
+
+log_formatters = {
+ 'raw': (formatters.JSONFormatter, "Raw structured log messages"),
+ 'unittest': (formatters.UnittestFormatter, "Unittest style output"),
+ 'xunit': (formatters.XUnitFormatter, "xUnit compatible XML"),
+ 'html': (formatters.HTMLFormatter, "HTML report"),
+ 'mach': (formatters.MachFormatter, "Human-readable output"),
+ 'tbpl': (formatters.TbplFormatter, "TBPL style log format"),
+}
+
+TEXT_FORMATTERS = ('raw', 'mach')
+"""a subset of formatters for non test harnesses related applications"""
+
+def level_filter_wrapper(formatter, level):
+ return handlers.LogLevelFilter(formatter, level)
+
+def verbose_wrapper(formatter, verbose):
+ formatter.verbose = verbose
+ return formatter
+
+def buffer_handler_wrapper(handler, buffer_limit):
+ if buffer_limit == "UNLIMITED":
+ buffer_limit = None
+ else:
+ buffer_limit = int(buffer_limit)
+ return handlers.BufferingLogFilter(handler, buffer_limit)
+
+formatter_option_defaults = {
+ 'verbose': False,
+ 'level': 'info',
+}
+
+fmt_options = {
+ # <option name>: (<wrapper function>, description, <applicable formatters>, action)
+ # "action" is used by the commandline parser in use.
+ 'verbose': (verbose_wrapper,
+ "Enables verbose mode for the given formatter.",
+ ["mach"], "store_true"),
+ 'level': (level_filter_wrapper,
+ "A least log level to subscribe to for the given formatter (debug, info, error, etc.)",
+ ["mach", "tbpl"], "store"),
+ 'buffer': (buffer_handler_wrapper,
+ "If specified, enables message buffering at the given buffer size limit.",
+ ["mach", "tbpl"], "store"),
+}
+
+
+def log_file(name):
+ if name == "-":
+ return sys.stdout
+ # ensure we have a correct dirpath by using realpath
+ dirpath = os.path.dirname(os.path.realpath(name))
+ if not os.path.exists(dirpath):
+ os.makedirs(dirpath)
+ return open(name, "w")
+
+
+def add_logging_group(parser, include_formatters=None):
+ """
+ Add logging options to an argparse ArgumentParser or
+ optparse OptionParser.
+
+ Each formatter has a corresponding option of the form --log-{name}
+ where {name} is the name of the formatter. The option takes a value
+ which is either a filename or "-" to indicate stdout.
+
+ :param parser: The ArgumentParser or OptionParser object that should have
+ logging options added.
+ :param include_formatters: List of formatter names that should be included
+ in the option group. Default to None, meaning
+ all the formatters are included. A common use
+ of this option is to specify
+ :data:`TEXT_FORMATTERS` to include only the
+ most useful formatters for a command line tool
+ that is not related to test harnesses.
+ """
+ group_name = "Output Logging"
+ group_description = ("Each option represents a possible logging format "
+ "and takes a filename to write that format to, "
+ "or '-' to write to stdout.")
+
+ if include_formatters is None:
+ include_formatters = log_formatters.keys()
+
+ if isinstance(parser, optparse.OptionParser):
+ group = optparse.OptionGroup(parser,
+ group_name,
+ group_description)
+ parser.add_option_group(group)
+ opt_log_type = 'str'
+ group_add = group.add_option
+ else:
+ group = parser.add_argument_group(group_name,
+ group_description)
+ opt_log_type = log_file
+ group_add = group.add_argument
+
+ for name, (cls, help_str) in log_formatters.iteritems():
+ if name in include_formatters:
+ group_add("--log-" + name, action="append", type=opt_log_type,
+ help=help_str)
+
+ for optname, (cls, help_str, formatters, action) in fmt_options.iteritems():
+ for fmt in formatters:
+ # make sure fmt is in log_formatters and is accepted
+ if fmt in log_formatters and fmt in include_formatters:
+ group_add("--log-%s-%s" % (fmt, optname), action=action,
+ help=help_str, default=None)
+
+
+def setup_handlers(logger, formatters, formatter_options):
+ """
+ Add handlers to the given logger according to the formatters and
+ options provided.
+
+ :param logger: The logger configured by this function.
+ :param formatters: A dict of {formatter, [streams]} to use in handlers.
+ :param formatter_options: a dict of {formatter: {option: value}} to
+ to use when configuring formatters.
+ """
+ unused_options = set(formatter_options.keys()) - set(formatters.keys())
+ if unused_options:
+ msg = ("Options specified for unused formatter(s) (%s) have no effect" %
+ list(unused_options))
+ raise ValueError(msg)
+
+ for fmt, streams in formatters.iteritems():
+ formatter_cls = log_formatters[fmt][0]
+ formatter = formatter_cls()
+ handler_wrapper, handler_option = None, ""
+ for option, value in formatter_options[fmt].iteritems():
+ if option == "buffer":
+ handler_wrapper, handler_option = fmt_options[option][0], value
+ else:
+ formatter = fmt_options[option][0](formatter, value)
+
+ for value in streams:
+ handler = handlers.StreamHandler(stream=value, formatter=formatter)
+ if handler_wrapper:
+ handler = handler_wrapper(handler, handler_option)
+ logger.add_handler(handler)
+
+
+def setup_logging(suite, args, defaults=None):
+ """
+ Configure a structuredlogger based on command line arguments.
+
+ The created structuredlogger will also be set as the default logger, and
+ can be retrieved with :py:func:`~mozlog.structured.structuredlog.get_default_logger`.
+
+ :param suite: The name of the testsuite being run
+ :param args: A dictionary of {argument_name:value} produced from
+ parsing the command line arguments for the application
+ :param defaults: A dictionary of {formatter name: output stream} to apply
+ when there is no logging supplied on the command line. If
+ this isn't supplied, reasonable defaults are chosen
+ (coloured mach formatting if stdout is a terminal, or raw
+ logs otherwise).
+
+ :rtype: StructuredLogger
+ """
+
+ logger = StructuredLogger(suite)
+ # Keep track of any options passed for formatters.
+ formatter_options = defaultdict(lambda: formatter_option_defaults.copy())
+ # Keep track of formatters and list of streams specified.
+ formatters = defaultdict(list)
+ found = False
+ found_stdout_logger = False
+ if not hasattr(args, 'iteritems'):
+ args = vars(args)
+
+ if defaults is None:
+ if sys.__stdout__.isatty():
+ defaults = {"mach": sys.stdout}
+ else:
+ defaults = {"raw": sys.stdout}
+
+ for name, values in args.iteritems():
+ parts = name.split('_')
+ if len(parts) > 3:
+ continue
+ # Our args will be ['log', <formatter>] or ['log', <formatter>, <option>].
+ if parts[0] == 'log' and values is not None:
+ if len(parts) == 1 or parts[1] not in log_formatters:
+ continue
+ if len(parts) == 2:
+ _, formatter = parts
+ for value in values:
+ found = True
+ if isinstance(value, basestring):
+ value = log_file(value)
+ if value == sys.stdout:
+ found_stdout_logger = True
+ formatters[formatter].append(value)
+ if len(parts) == 3:
+ _, formatter, opt = parts
+ formatter_options[formatter][opt] = values
+
+ #If there is no user-specified logging, go with the default options
+ if not found:
+ for name, value in defaults.iteritems():
+ formatters[name].append(value)
+
+ elif not found_stdout_logger and sys.stdout in defaults.values():
+ for name, value in defaults.iteritems():
+ if value == sys.stdout:
+ formatters[name].append(value)
+
+ setup_handlers(logger, formatters, formatter_options)
+ set_default_logger(logger)
+
+ return logger