123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- # Licensed under the Apache License, Version 2.0 (the "License"); you may
- # not use this file except in compliance with the License. You may obtain
- # a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- # License for the specific language governing permissions and limitations
- # under the License.
- import datetime
- import inspect
- import logging
- import logging.handlers
- import numbers
- import os
- import sys
- try:
- import syslog
- except ImportError:
- syslog = None
- from daiquiri import formatter
- from daiquiri import handlers
- def get_program_name():
- return os.path.basename(inspect.stack()[-1][1])
- class Output(object):
- """Generic log output."""
- def __init__(self, handler, formatter=formatter.TEXT_FORMATTER,
- level=None):
- self.handler = handler
- self.handler.setFormatter(formatter)
- if level is not None:
- self.handler.setLevel(level)
- def add_to_logger(self, logger):
- """Add this output to a logger."""
- logger.addHandler(self.handler)
- def _get_log_file_path(logfile=None, logdir=None, program_name=None,
- logfile_suffix=".log"):
- ret_path = None
- if not logdir:
- ret_path = logfile
- if not ret_path and logfile and logdir:
- ret_path = os.path.join(logdir, logfile)
- if not ret_path and logdir:
- program_name = program_name or get_program_name()
- ret_path = os.path.join(logdir, program_name) + logfile_suffix
- if not ret_path:
- raise ValueError("Unable to determine log file destination")
- return ret_path
- class File(Output):
- """Ouput to a file."""
- def __init__(self, filename=None, directory=None, suffix=".log",
- program_name=None, formatter=formatter.TEXT_FORMATTER,
- level=None):
- """Log file output.
- :param filename: The log file path to write to.
- If directory is also specified, both will be combined.
- :param directory: The log directory to write to.
- If no filename is specified, the program name and suffix will be used
- to contruct the full path relative to the directory.
- :param suffix: The log file name suffix.
- This will be only used if no filename has been provided.
- :param program_name: Program name. Autodetected by default.
- """
- logpath = _get_log_file_path(filename, directory,
- program_name, suffix)
- handler = logging.handlers.WatchedFileHandler(logpath)
- super(File, self).__init__(handler, formatter, level)
- class RotatingFile(Output):
- """Output to a file, rotating after a certain size."""
- def __init__(self, filename=None, directory=None, suffix='.log',
- program_name=None, formatter=formatter.TEXT_FORMATTER,
- level=None, max_size_bytes=0, backup_count=0):
- """Rotating log file output.
- :param filename: The log file path to write to.
- If directory is also specified, both will be combined.
- :param directory: The log directory to write to.
- If no filename is specified, the program name and suffix will be used
- to contruct the full path relative to the directory.
- :param suffix: The log file name suffix.
- This will be only used if no filename has been provided.
- :param program_name: Program name. Autodetected by default.
- :param max_size_bytes: allow the file to rollover at a
- predetermined size.
- :param backup_count: the maximum number of files to rotate
- logging output between.
- """
- logpath = _get_log_file_path(filename, directory,
- program_name, suffix)
- handler = logging.handlers.RotatingFileHandler(
- logpath, maxBytes=max_size_bytes, backupCount=backup_count)
- super(RotatingFile, self).__init__(handler, formatter, level)
- def do_rollover(self):
- """Manually forces a log file rotation."""
- return self.handler.doRollover()
- class TimedRotatingFile(Output):
- """Rotating log file output, triggered by a fixed interval."""
- def __init__(self, filename=None, directory=None, suffix='.log',
- program_name=None, formatter=formatter.TEXT_FORMATTER,
- level=None, interval=datetime.timedelta(hours=24),
- backup_count=0):
- """Rotating log file output, triggered by a fixed interval.
- :param filename: The log file path to write to.
- If directory is also specified, both will be combined.
- :param directory: The log directory to write to.
- If no filename is specified, the program name and suffix will be used
- to contruct the full path relative to the directory.
- :param suffix: The log file name suffix.
- This will be only used if no filename has been provided.
- :param program_name: Program name. Autodetected by default.
- :param interval: datetime.timedelta instance representing
- how often a new log file should be created.
- :param backup_count: the maximum number of files to rotate
- logging output between.
- """
- logpath = _get_log_file_path(filename, directory,
- program_name, suffix)
- handler = logging.handlers.TimedRotatingFileHandler(
- logpath,
- when='S',
- interval=self._timedelta_to_seconds(interval),
- backupCount=backup_count)
- super(TimedRotatingFile, self).__init__(handler, formatter, level)
- def do_rollover(self):
- """Manually forces a log file rotation."""
- return self.handler.doRollover()
- @staticmethod
- def _timedelta_to_seconds(td):
- """Convert a datetime.timedelta object into a seconds interval for
- rotating file ouput.
- :param td: datetime.timedelta
- :return: time in seconds
- :rtype: int
- """
- if isinstance(td, numbers.Real):
- td = datetime.timedelta(seconds=td)
- return td.total_seconds()
- class Stream(Output):
- """Generic stream output."""
- def __init__(self, stream=sys.stderr, formatter=formatter.TEXT_FORMATTER,
- level=None):
- super(Stream, self).__init__(handlers.TTYDetectorStreamHandler(stream),
- formatter, level)
- STDERR = Stream()
- STDOUT = Stream(sys.stdout)
- class Journal(Output):
- def __init__(self, program_name=None,
- formatter=formatter.TEXT_FORMATTER, level=None):
- program_name = program_name or get_program_name
- super(Journal, self).__init__(handlers.JournalHandler(program_name),
- formatter, level)
- class Syslog(Output):
- def __init__(self, program_name=None, facility="user",
- formatter=formatter.TEXT_FORMATTER, level=None):
- if syslog is None:
- # FIXME(jd) raise something more specific
- raise RuntimeError("syslog is not available on this platform")
- super(Syslog, self).__init__(
- handlers.SyslogHandler(
- program_name=program_name or get_program_name(),
- facility=self._find_facility(facility)),
- formatter, level)
- @staticmethod
- def _find_facility(facility):
- # NOTE(jd): Check the validity of facilities at run time as they differ
- # depending on the OS and Python version being used.
- valid_facilities = [f for f in
- ["LOG_KERN", "LOG_USER", "LOG_MAIL",
- "LOG_DAEMON", "LOG_AUTH", "LOG_SYSLOG",
- "LOG_LPR", "LOG_NEWS", "LOG_UUCP",
- "LOG_CRON", "LOG_AUTHPRIV", "LOG_FTP",
- "LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2",
- "LOG_LOCAL3", "LOG_LOCAL4", "LOG_LOCAL5",
- "LOG_LOCAL6", "LOG_LOCAL7"]
- if getattr(syslog, f, None)]
- facility = facility.upper()
- if not facility.startswith("LOG_"):
- facility = "LOG_" + facility
- if facility not in valid_facilities:
- raise TypeError('syslog facility must be one of: %s' %
- ', '.join("'%s'" % fac
- for fac in valid_facilities))
- return getattr(syslog, facility)
- preconfigured = {
- 'stderr': STDERR,
- 'stdout': STDOUT,
- }
- if syslog is not None:
- preconfigured['syslog'] = Syslog()
- if handlers.journal is not None:
- preconfigured['journal'] = Journal()
|