1# sqlalchemy/log.py 
    2# Copyright (C) 2006-2021 the SQLAlchemy authors and contributors 
    3# <see AUTHORS file> 
    4# Includes alterations by Vinay Sajip vinay_sajip@yahoo.co.uk 
    5# 
    6# This module is part of SQLAlchemy and is released under 
    7# the MIT License: http://www.opensource.org/licenses/mit-license.php 
    8 
    9"""Logging control and utilities. 
    10 
    11Control of logging for SA can be performed from the regular python logging 
    12module.  The regular dotted module namespace is used, starting at 
    13'sqlalchemy'.  For class-level logging, the class name is appended. 
    14 
    15The "echo" keyword parameter, available on SQLA :class:`_engine.Engine` 
    16and :class:`_pool.Pool` objects, corresponds to a logger specific to that 
    17instance only. 
    18 
    19""" 
    20 
    21import logging 
    22import sys 
    23 
    24 
    25# set initial level to WARN.  This so that 
    26# log statements don't occur in the absence of explicit 
    27# logging being enabled for 'sqlalchemy'. 
    28rootlogger = logging.getLogger("sqlalchemy") 
    29if rootlogger.level == logging.NOTSET: 
    30    rootlogger.setLevel(logging.WARN) 
    31 
    32 
    33def _add_default_handler(logger): 
    34    handler = logging.StreamHandler(sys.stdout) 
    35    handler.setFormatter( 
    36        logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") 
    37    ) 
    38    logger.addHandler(handler) 
    39 
    40 
    41_logged_classes = set() 
    42 
    43 
    44def class_logger(cls): 
    45    logger = logging.getLogger(cls.__module__ + "." + cls.__name__) 
    46    cls._should_log_debug = lambda self: logger.isEnabledFor(logging.DEBUG) 
    47    cls._should_log_info = lambda self: logger.isEnabledFor(logging.INFO) 
    48    cls.logger = logger 
    49    _logged_classes.add(cls) 
    50    return cls 
    51 
    52 
    53class Identified(object): 
    54    logging_name = None 
    55 
    56    def _should_log_debug(self): 
    57        return self.logger.isEnabledFor(logging.DEBUG) 
    58 
    59    def _should_log_info(self): 
    60        return self.logger.isEnabledFor(logging.INFO) 
    61 
    62 
    63class InstanceLogger(object): 
    64    """A logger adapter (wrapper) for :class:`.Identified` subclasses. 
    65 
    66    This allows multiple instances (e.g. Engine or Pool instances) 
    67    to share a logger, but have its verbosity controlled on a 
    68    per-instance basis. 
    69 
    70    The basic functionality is to return a logging level 
    71    which is based on an instance's echo setting. 
    72 
    73    Default implementation is: 
    74 
    75    'debug' -> logging.DEBUG 
    76    True    -> logging.INFO 
    77    False   -> Effective level of underlying logger ( 
    78    logging.WARNING by default) 
    79    None    -> same as False 
    80    """ 
    81 
    82    # Map echo settings to logger levels 
    83    _echo_map = { 
    84        None: logging.NOTSET, 
    85        False: logging.NOTSET, 
    86        True: logging.INFO, 
    87        "debug": logging.DEBUG, 
    88    } 
    89 
    90    def __init__(self, echo, name): 
    91        self.echo = echo 
    92        self.logger = logging.getLogger(name) 
    93 
    94        # if echo flag is enabled and no handlers, 
    95        # add a handler to the list 
    96        if self._echo_map[echo] <= logging.INFO and not self.logger.handlers: 
    97            _add_default_handler(self.logger) 
    98 
    99    # 
    100    # Boilerplate convenience methods 
    101    # 
    102    def debug(self, msg, *args, **kwargs): 
    103        """Delegate a debug call to the underlying logger.""" 
    104 
    105        self.log(logging.DEBUG, msg, *args, **kwargs) 
    106 
    107    def info(self, msg, *args, **kwargs): 
    108        """Delegate an info call to the underlying logger.""" 
    109 
    110        self.log(logging.INFO, msg, *args, **kwargs) 
    111 
    112    def warning(self, msg, *args, **kwargs): 
    113        """Delegate a warning call to the underlying logger.""" 
    114 
    115        self.log(logging.WARNING, msg, *args, **kwargs) 
    116 
    117    warn = warning 
    118 
    119    def error(self, msg, *args, **kwargs): 
    120        """ 
    121        Delegate an error call to the underlying logger. 
    122        """ 
    123        self.log(logging.ERROR, msg, *args, **kwargs) 
    124 
    125    def exception(self, msg, *args, **kwargs): 
    126        """Delegate an exception call to the underlying logger.""" 
    127 
    128        kwargs["exc_info"] = 1 
    129        self.log(logging.ERROR, msg, *args, **kwargs) 
    130 
    131    def critical(self, msg, *args, **kwargs): 
    132        """Delegate a critical call to the underlying logger.""" 
    133 
    134        self.log(logging.CRITICAL, msg, *args, **kwargs) 
    135 
    136    def log(self, level, msg, *args, **kwargs): 
    137        """Delegate a log call to the underlying logger. 
    138 
    139        The level here is determined by the echo 
    140        flag as well as that of the underlying logger, and 
    141        logger._log() is called directly. 
    142 
    143        """ 
    144 
    145        # inline the logic from isEnabledFor(), 
    146        # getEffectiveLevel(), to avoid overhead. 
    147 
    148        if self.logger.manager.disable >= level: 
    149            return 
    150 
    151        selected_level = self._echo_map[self.echo] 
    152        if selected_level == logging.NOTSET: 
    153            selected_level = self.logger.getEffectiveLevel() 
    154 
    155        if level >= selected_level: 
    156            self.logger._log(level, msg, args, **kwargs) 
    157 
    158    def isEnabledFor(self, level): 
    159        """Is this logger enabled for level 'level'?""" 
    160 
    161        if self.logger.manager.disable >= level: 
    162            return False 
    163        return level >= self.getEffectiveLevel() 
    164 
    165    def getEffectiveLevel(self): 
    166        """What's the effective level for this logger?""" 
    167 
    168        level = self._echo_map[self.echo] 
    169        if level == logging.NOTSET: 
    170            level = self.logger.getEffectiveLevel() 
    171        return level 
    172 
    173 
    174def instance_logger(instance, echoflag=None): 
    175    """create a logger for an instance that implements :class:`.Identified`.""" 
    176 
    177    if instance.logging_name: 
    178        name = "%s.%s.%s" % ( 
    179            instance.__class__.__module__, 
    180            instance.__class__.__name__, 
    181            instance.logging_name, 
    182        ) 
    183    else: 
    184        name = "%s.%s" % ( 
    185            instance.__class__.__module__, 
    186            instance.__class__.__name__, 
    187        ) 
    188 
    189    instance._echo = echoflag 
    190 
    191    if echoflag in (False, None): 
    192        # if no echo setting or False, return a Logger directly, 
    193        # avoiding overhead of filtering 
    194        logger = logging.getLogger(name) 
    195    else: 
    196        # if a specified echo flag, return an EchoLogger, 
    197        # which checks the flag, overrides normal log 
    198        # levels by calling logger._log() 
    199        logger = InstanceLogger(echoflag, name) 
    200 
    201    instance.logger = logger 
    202 
    203 
    204class echo_property(object): 
    205    __doc__ = """\ 
    206    When ``True``, enable log output for this element. 
    207 
    208    This has the effect of setting the Python logging level for the namespace 
    209    of this element's class and object reference.  A value of boolean ``True`` 
    210    indicates that the loglevel ``logging.INFO`` will be set for the logger, 
    211    whereas the string value ``debug`` will set the loglevel to 
    212    ``logging.DEBUG``. 
    213    """ 
    214 
    215    def __get__(self, instance, owner): 
    216        if instance is None: 
    217            return self 
    218        else: 
    219            return instance._echo 
    220 
    221    def __set__(self, instance, value): 
    222        instance_logger(instance, echoflag=value)