Index: infra_libs/logs/logs.py |
diff --git a/infra_libs/logs/logs.py b/infra_libs/logs/logs.py |
deleted file mode 100644 |
index 2a32786d825f49164ea6ca0c515281d650b3f678..0000000000000000000000000000000000000000 |
--- a/infra_libs/logs/logs.py |
+++ /dev/null |
@@ -1,295 +0,0 @@ |
-# Copyright 2014 The Chromium Authors. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-"""Utilities for logging. |
- |
-Example usage: |
- |
-.. code-block:: python |
- |
- import argparse |
- import logging |
- import infra_libs.logs |
- |
- parser = argparse.ArgumentParser() |
- infra_libs.logs.add_argparse_options(parser) |
- |
- options = parser.parse_args() |
- infra_libs.logs.process_argparse_options(options) |
- |
- LOGGER = logging.getLogger(__name__) |
- LOGGER.info('test message') |
- |
-The last line should print something like:: |
- |
- [I2014-06-27T11:42:32.418716-07:00 7082 logs:71] test message |
- |
-""" |
- |
-import datetime |
-import getpass |
-import inspect |
-import logging |
-import logging.handlers |
-import os |
-import re |
-import socket |
-import sys |
-import tempfile |
-import textwrap |
- |
-import pytz |
- |
-from infra_libs.ts_mon.common.metrics import CumulativeMetric |
- |
-log_metric = CumulativeMetric( |
- 'proc/log_lines', description="Number of log lines, per severity level.") |
- |
-if sys.platform.startswith('win'): # pragma: no cover |
- DEFAULT_LOG_DIRECTORIES = os.pathsep.join([ |
- 'E:\\chrome-infra-logs', |
- 'C:\\chrome-infra-logs', |
- ]) |
-else: |
- DEFAULT_LOG_DIRECTORIES = '/var/log/chrome-infra' |
- |
- |
-class InfraFilter(logging.Filter): # pragma: no cover |
- """Adds fields used by the infra-specific formatter. |
- |
- Fields added: |
- |
- - 'iso8601': timestamp |
- - 'severity': one-letter indicator of log level (first letter of levelname). |
- - 'fullModuleName': name of the module including the package name. |
- |
- Args: |
- timezone (str): timezone in which timestamps should be printed. |
- module_name_blacklist (str): do not print log lines from modules whose name |
- matches this regular expression. |
- """ |
- def __init__(self, timezone, module_name_blacklist=None): |
- super(InfraFilter, self).__init__() |
- self.module_name_blacklist = None |
- |
- if module_name_blacklist: |
- self.module_name_blacklist = re.compile(module_name_blacklist) |
- |
- self.tz = pytz.timezone(timezone) |
- |
- def filter(self, record): |
- dt = datetime.datetime.fromtimestamp(record.created, tz=pytz.utc) |
- record.iso8601 = self.tz.normalize(dt).isoformat() |
- record.severity = record.levelname[0] |
- log_metric.increment(fields={'level': record.severity}) |
- record.fullModuleName = self._full_module_name() or record.module |
- if self.module_name_blacklist: |
- if self.module_name_blacklist.search(record.fullModuleName): |
- return False |
- return True |
- |
- def _full_module_name(self): |
- frame = inspect.currentframe() |
- try: |
- while frame is not None: |
- try: |
- name = frame.f_globals['__name__'] |
- except KeyError: |
- continue |
- |
- if name not in (__name__, 'logging'): |
- return name |
- frame = frame.f_back |
- finally: |
- del frame |
- |
- return None |
- |
- |
-class InfraFormatter(logging.Formatter): # pragma: no cover |
- """Formats log messages in a standard way. |
- |
- This object processes fields added by :class:`InfraFilter`. |
- """ |
- def __init__(self): |
- super(InfraFormatter, self).__init__( |
- '[%(severity)s%(iso8601)s %(process)d %(thread)d ' |
- '%(fullModuleName)s:%(lineno)s] %(message)s') |
- |
- |
-def add_handler(logger, handler=None, timezone='UTC', |
- level=logging.WARNING, |
- module_name_blacklist=None): # pragma: no cover |
- """Configures and adds a handler to a logger the standard way for infra. |
- |
- Args: |
- logger (logging.Logger): logger object obtained from `logging.getLogger`. |
- |
- Keyword Args: |
- handler (logging.Handler): handler to add to the logger. defaults to |
- logging.StreamHandler. |
- timezone (str): timezone to use for timestamps. |
- level (int): logging level. Could be one of DEBUG, INFO, WARNING, CRITICAL |
- module_name_blacklist (str): do not print log lines from modules whose name |
- matches this regular expression. |
- |
- Example usage:: |
- |
- import logging |
- import infra_libs.logs |
- logger = logging.getLogger('foo') |
- infra_libs.logs.add_handler(logger, timezone='US/Pacific') |
- logger.info('test message') |
- |
- The last line should print something like:: |
- |
- [I2014-06-27T11:42:32.418716-07:00 7082 logs:71] test message |
- |
- """ |
- handler = handler or logging.StreamHandler() |
- handler.addFilter(InfraFilter(timezone, |
- module_name_blacklist=module_name_blacklist)) |
- handler.setFormatter(InfraFormatter()) |
- handler.setLevel(level=level) |
- logger.addHandler(handler) |
- |
- # Formatters only get messages that pass this filter: let everything through. |
- logger.setLevel(level=logging.DEBUG) |
- |
- |
-def default_program_name(): |
- # Use argv[0] as the program name, except when it's '__main__.py' which is the |
- # case when we were invoked by run.py. In this case look at the main module's |
- # __package__ variable which is set by runpy. |
- ret = os.path.basename(sys.argv[0]) |
- if ret == '__main__.py': |
- package = sys.modules['__main__'].__package__ |
- if package is not None: |
- return package.split('.')[-1] |
- return ret |
- |
- |
-def add_argparse_options(parser, |
- default_level=logging.WARNING): # pragma: no cover |
- """Adds logging related options to an argparse.ArgumentParser. |
- |
- See also: :func:`process_argparse_options` |
- """ |
- |
- parser = parser.add_argument_group('Logging Options') |
- g = parser.add_mutually_exclusive_group() |
- g.set_defaults(log_level=default_level) |
- g.add_argument('--logs-quiet', '--quiet', |
- action='store_const', const=logging.ERROR, |
- dest='log_level', help='Make the output quieter (ERROR).') |
- g.add_argument('--logs-warning', '--warning', |
- action='store_const', const=logging.WARNING, |
- dest='log_level', |
- help='Set the output to an average verbosity (WARNING).') |
- g.add_argument('--logs-verbose', '--verbose', |
- action='store_const', const=logging.INFO, |
- dest='log_level', help='Make the output louder (INFO).') |
- g.add_argument('--logs-debug', '--debug', |
- action='store_const', const=logging.DEBUG, |
- dest='log_level', help='Make the output really loud (DEBUG).') |
- parser.add_argument('--logs-black-list', metavar='REGEX', |
- help='hide log lines emitted by modules whose name ' |
- 'matches\nthis regular expression.') |
- parser.add_argument( |
- '--logs-directory', |
- default=DEFAULT_LOG_DIRECTORIES, |
- help=textwrap.fill( |
- 'directory into which to write logs (default: %%(default)s). If ' |
- 'this directory does not exist or is not writable, the temporary ' |
- 'directory (%s) will be used instead. If this is explicitly set ' |
- 'to the empty string, logs will not be written at all. May be set ' |
- 'to multiple directories separated by the "%s" character, in ' |
- 'which case the first one that exists and is writable is used.' % ( |
- tempfile.gettempdir(), os.pathsep), width=56)) |
- parser.add_argument( |
- '--logs-program-name', |
- default=default_program_name(), |
- help='the program name used to name the log files created in ' |
- '--logs-directory (default: %(default)s).') |
- parser.add_argument( |
- '--logs-debug-file', |
- action='store_true', |
- help='by default only INFO, WARNING and ERROR log files are written to ' |
- 'disk. This flag causes a DEBUG log to be written as well.') |
- |
- |
-def process_argparse_options(options, logger=None): # pragma: no cover |
- """Handles logging argparse options added in 'add_argparse_options'. |
- |
- Configures 'logging' module. |
- |
- Args: |
- options: return value of argparse.ArgumentParser.parse_args. |
- logger (logging.Logger): logger to apply the configuration to. |
- |
- Example usage:: |
- |
- import argparse |
- import sys |
- import infra_libs.logs |
- |
- parser = argparse.ArgumentParser() |
- infra_libs.logs.add_argparse_options(parser) |
- |
- options = parser.parse_args(sys.path[1:]) |
- infra_libs.logs.process_argparse_options(options) |
- """ |
- |
- if logger is None: |
- logger = logging.root |
- |
- add_handler(logger, level=options.log_level, |
- module_name_blacklist=options.logs_black_list) |
- |
- if options.logs_directory: |
- _add_file_handlers(options, logger) |
- |
- |
-def _add_file_handlers(options, logger): # pragma: no cover |
- # Test whether we can write to the log directory. If not, write to a |
- # temporary directory instead. One of the DEFAULT_LOG_DIRECTORIES are created |
- # on the real production machines by puppet, so /tmp should only be used when |
- # running locally on developers' workstations. |
- logs_directory = tempfile.gettempdir() |
- for directory in options.logs_directory.split(os.pathsep): |
- if not os.path.isdir(directory): |
- continue |
- |
- try: |
- with tempfile.TemporaryFile(dir=directory): |
- pass |
- except OSError: |
- pass |
- else: |
- logs_directory = directory |
- break |
- |
- # Log files are named with this pattern: |
- # <program>.<hostname>.<username>.log.<level>.YYYYMMDD-HHMMSS.<pid> |
- pattern = "%s.%s.%s.log.%%s.%s.%d" % ( |
- options.logs_program_name, |
- socket.getfqdn().split('.')[0], |
- getpass.getuser(), |
- datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S'), |
- os.getpid()) |
- |
- file_levels = [logging.INFO, logging.WARNING, logging.ERROR] |
- if options.logs_debug_file: |
- file_levels.append(logging.DEBUG) |
- |
- for level in file_levels: |
- add_handler( |
- logger, |
- handler=logging.handlers.RotatingFileHandler( |
- filename=os.path.join( |
- logs_directory, pattern % logging.getLevelName(level)), |
- maxBytes=10 * 1024 * 1024, |
- backupCount=10, |
- delay=True), |
- level=level) |