Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/log.py: 57%
90 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# sqlalchemy/log.py
2# Copyright (C) 2006-2023 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: https://www.opensource.org/licenses/mit-license.php
9"""Logging control and utilities.
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.
15The "echo" keyword parameter, available on SQLA :class:`_engine.Engine`
16and :class:`_pool.Pool` objects, corresponds to a logger specific to that
17instance only.
19"""
21import logging
22import sys
24from .util import py311
25from .util import py38
27if py38:
28 STACKLEVEL = True
29 # needed as of py3.11.0b1
30 # #8019
31 STACKLEVEL_OFFSET = 2 if py311 else 1
32else:
33 STACKLEVEL = False
34 STACKLEVEL_OFFSET = 0
36# set initial level to WARN. This so that
37# log statements don't occur in the absence of explicit
38# logging being enabled for 'sqlalchemy'.
39rootlogger = logging.getLogger("sqlalchemy")
40if rootlogger.level == logging.NOTSET:
41 rootlogger.setLevel(logging.WARN)
44def _add_default_handler(logger):
45 handler = logging.StreamHandler(sys.stdout)
46 handler.setFormatter(
47 logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
48 )
49 logger.addHandler(handler)
52_logged_classes = set()
55def _qual_logger_name_for_cls(cls):
56 return (
57 getattr(cls, "_sqla_logger_namespace", None)
58 or cls.__module__ + "." + cls.__name__
59 )
62def class_logger(cls):
63 logger = logging.getLogger(_qual_logger_name_for_cls(cls))
64 cls._should_log_debug = lambda self: logger.isEnabledFor(logging.DEBUG)
65 cls._should_log_info = lambda self: logger.isEnabledFor(logging.INFO)
66 cls.logger = logger
67 _logged_classes.add(cls)
68 return cls
71class Identified(object):
72 logging_name = None
74 def _should_log_debug(self):
75 return self.logger.isEnabledFor(logging.DEBUG)
77 def _should_log_info(self):
78 return self.logger.isEnabledFor(logging.INFO)
81class InstanceLogger(object):
82 """A logger adapter (wrapper) for :class:`.Identified` subclasses.
84 This allows multiple instances (e.g. Engine or Pool instances)
85 to share a logger, but have its verbosity controlled on a
86 per-instance basis.
88 The basic functionality is to return a logging level
89 which is based on an instance's echo setting.
91 Default implementation is:
93 'debug' -> logging.DEBUG
94 True -> logging.INFO
95 False -> Effective level of underlying logger (
96 logging.WARNING by default)
97 None -> same as False
98 """
100 # Map echo settings to logger levels
101 _echo_map = {
102 None: logging.NOTSET,
103 False: logging.NOTSET,
104 True: logging.INFO,
105 "debug": logging.DEBUG,
106 }
108 def __init__(self, echo, name):
109 self.echo = echo
110 self.logger = logging.getLogger(name)
112 # if echo flag is enabled and no handlers,
113 # add a handler to the list
114 if self._echo_map[echo] <= logging.INFO and not self.logger.handlers:
115 _add_default_handler(self.logger)
117 #
118 # Boilerplate convenience methods
119 #
120 def debug(self, msg, *args, **kwargs):
121 """Delegate a debug call to the underlying logger."""
123 self.log(logging.DEBUG, msg, *args, **kwargs)
125 def info(self, msg, *args, **kwargs):
126 """Delegate an info call to the underlying logger."""
128 self.log(logging.INFO, msg, *args, **kwargs)
130 def warning(self, msg, *args, **kwargs):
131 """Delegate a warning call to the underlying logger."""
133 self.log(logging.WARNING, msg, *args, **kwargs)
135 warn = warning
137 def error(self, msg, *args, **kwargs):
138 """
139 Delegate an error call to the underlying logger.
140 """
141 self.log(logging.ERROR, msg, *args, **kwargs)
143 def exception(self, msg, *args, **kwargs):
144 """Delegate an exception call to the underlying logger."""
146 kwargs["exc_info"] = 1
147 self.log(logging.ERROR, msg, *args, **kwargs)
149 def critical(self, msg, *args, **kwargs):
150 """Delegate a critical call to the underlying logger."""
152 self.log(logging.CRITICAL, msg, *args, **kwargs)
154 def log(self, level, msg, *args, **kwargs):
155 """Delegate a log call to the underlying logger.
157 The level here is determined by the echo
158 flag as well as that of the underlying logger, and
159 logger._log() is called directly.
161 """
163 # inline the logic from isEnabledFor(),
164 # getEffectiveLevel(), to avoid overhead.
166 if self.logger.manager.disable >= level:
167 return
169 selected_level = self._echo_map[self.echo]
170 if selected_level == logging.NOTSET:
171 selected_level = self.logger.getEffectiveLevel()
173 if level >= selected_level:
174 if STACKLEVEL:
175 kwargs["stacklevel"] = (
176 kwargs.get("stacklevel", 1) + STACKLEVEL_OFFSET
177 )
179 self.logger._log(level, msg, args, **kwargs)
181 def isEnabledFor(self, level):
182 """Is this logger enabled for level 'level'?"""
184 if self.logger.manager.disable >= level:
185 return False
186 return level >= self.getEffectiveLevel()
188 def getEffectiveLevel(self):
189 """What's the effective level for this logger?"""
191 level = self._echo_map[self.echo]
192 if level == logging.NOTSET:
193 level = self.logger.getEffectiveLevel()
194 return level
197def instance_logger(instance, echoflag=None):
198 """create a logger for an instance that implements :class:`.Identified`."""
200 if instance.logging_name:
201 name = "%s.%s" % (
202 _qual_logger_name_for_cls(instance.__class__),
203 instance.logging_name,
204 )
205 else:
206 name = _qual_logger_name_for_cls(instance.__class__)
208 instance._echo = echoflag
210 if echoflag in (False, None):
211 # if no echo setting or False, return a Logger directly,
212 # avoiding overhead of filtering
213 logger = logging.getLogger(name)
214 else:
215 # if a specified echo flag, return an EchoLogger,
216 # which checks the flag, overrides normal log
217 # levels by calling logger._log()
218 logger = InstanceLogger(echoflag, name)
220 instance.logger = logger
223class echo_property(object):
224 __doc__ = """\
225 When ``True``, enable log output for this element.
227 This has the effect of setting the Python logging level for the namespace
228 of this element's class and object reference. A value of boolean ``True``
229 indicates that the loglevel ``logging.INFO`` will be set for the logger,
230 whereas the string value ``debug`` will set the loglevel to
231 ``logging.DEBUG``.
232 """
234 def __get__(self, instance, owner):
235 if instance is None:
236 return self
237 else:
238 return instance._echo
240 def __set__(self, instance, value):
241 instance_logger(instance, echoflag=value)