Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/log.py: 66%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# log.py
2# Copyright (C) 2006-2026 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"""
21from __future__ import annotations
23import logging
24import sys
25from typing import Any
26from typing import Literal
27from typing import Optional
28from typing import overload
29from typing import Set
30from typing import Type
31from typing import TypeVar
32from typing import Union
34STACKLEVEL = True
35STACKLEVEL_OFFSET = 2
37_IT = TypeVar("_IT", bound="Identified")
39_EchoFlagType = Union[None, bool, Literal["debug"]]
41# set initial level to WARN. This so that
42# log statements don't occur in the absence of explicit
43# logging being enabled for 'sqlalchemy'.
44rootlogger = logging.getLogger("sqlalchemy")
45if rootlogger.level == logging.NOTSET:
46 rootlogger.setLevel(logging.WARNING)
49def _add_default_handler(logger: logging.Logger) -> None:
50 handler = logging.StreamHandler(sys.stdout)
51 handler.setFormatter(
52 logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
53 )
54 logger.addHandler(handler)
57_logged_classes: Set[Type[Identified]] = set()
60def _qual_logger_name_for_cls(cls: Type[Identified]) -> str:
61 return (
62 getattr(cls, "_sqla_logger_namespace", None)
63 or cls.__module__ + "." + cls.__name__
64 )
67def class_logger(cls: Type[_IT]) -> Type[_IT]:
68 logger = logging.getLogger(_qual_logger_name_for_cls(cls))
69 cls._should_log_debug = lambda self: logger.isEnabledFor( # type: ignore[method-assign] # noqa: E501
70 logging.DEBUG
71 )
72 cls._should_log_info = lambda self: logger.isEnabledFor( # type: ignore[method-assign] # noqa: E501
73 logging.INFO
74 )
75 cls.logger = logger
76 _logged_classes.add(cls)
77 return cls
80_IdentifiedLoggerType = Union[logging.Logger, "InstanceLogger"]
83class Identified:
84 __slots__ = ()
86 logging_name: Optional[str] = None
88 logger: _IdentifiedLoggerType
90 _echo: _EchoFlagType
92 def _should_log_debug(self) -> bool:
93 return self.logger.isEnabledFor(logging.DEBUG)
95 def _should_log_info(self) -> bool:
96 return self.logger.isEnabledFor(logging.INFO)
99class InstanceLogger:
100 """A logger adapter (wrapper) for :class:`.Identified` subclasses.
102 This allows multiple instances (e.g. Engine or Pool instances)
103 to share a logger, but have its verbosity controlled on a
104 per-instance basis.
106 The basic functionality is to return a logging level
107 which is based on an instance's echo setting.
109 Default implementation is:
111 'debug' -> logging.DEBUG
112 True -> logging.INFO
113 False -> Effective level of underlying logger (
114 logging.WARNING by default)
115 None -> same as False
116 """
118 # Map echo settings to logger levels
119 _echo_map = {
120 None: logging.NOTSET,
121 False: logging.NOTSET,
122 True: logging.INFO,
123 "debug": logging.DEBUG,
124 }
126 _echo: _EchoFlagType
128 __slots__ = ("echo", "logger")
130 def __init__(self, echo: _EchoFlagType, name: str):
131 self.echo = echo
132 self.logger = logging.getLogger(name)
134 # if echo flag is enabled and no handlers,
135 # add a handler to the list
136 if self._echo_map[echo] <= logging.INFO and not self.logger.handlers:
137 _add_default_handler(self.logger)
139 #
140 # Boilerplate convenience methods
141 #
142 def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
143 """Delegate a debug call to the underlying logger."""
145 self.log(logging.DEBUG, msg, *args, **kwargs)
147 def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
148 """Delegate an info call to the underlying logger."""
150 self.log(logging.INFO, msg, *args, **kwargs)
152 def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
153 """Delegate a warning call to the underlying logger."""
155 self.log(logging.WARNING, msg, *args, **kwargs)
157 warn = warning
159 def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
160 """
161 Delegate an error call to the underlying logger.
162 """
163 self.log(logging.ERROR, msg, *args, **kwargs)
165 def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
166 """Delegate an exception call to the underlying logger."""
168 kwargs["exc_info"] = 1
169 self.log(logging.ERROR, msg, *args, **kwargs)
171 def critical(self, msg: str, *args: Any, **kwargs: Any) -> None:
172 """Delegate a critical call to the underlying logger."""
174 self.log(logging.CRITICAL, msg, *args, **kwargs)
176 def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
177 """Delegate a log call to the underlying logger.
179 The level here is determined by the echo
180 flag as well as that of the underlying logger, and
181 logger._log() is called directly.
183 """
185 # inline the logic from isEnabledFor(),
186 # getEffectiveLevel(), to avoid overhead.
188 if self.logger.manager.disable >= level:
189 return
191 selected_level = self._echo_map[self.echo]
192 if selected_level == logging.NOTSET:
193 selected_level = self.logger.getEffectiveLevel()
195 if level >= selected_level:
196 if STACKLEVEL:
197 kwargs["stacklevel"] = (
198 kwargs.get("stacklevel", 1) + STACKLEVEL_OFFSET
199 )
201 self.logger._log(level, msg, args, **kwargs)
203 def isEnabledFor(self, level: int) -> bool:
204 """Is this logger enabled for level 'level'?"""
206 if self.logger.manager.disable >= level:
207 return False
208 return level >= self.getEffectiveLevel()
210 def getEffectiveLevel(self) -> int:
211 """What's the effective level for this logger?"""
213 level = self._echo_map[self.echo]
214 if level == logging.NOTSET:
215 level = self.logger.getEffectiveLevel()
216 return level
219def instance_logger(
220 instance: Identified, echoflag: _EchoFlagType = None
221) -> None:
222 """create a logger for an instance that implements :class:`.Identified`."""
224 if instance.logging_name:
225 name = "%s.%s" % (
226 _qual_logger_name_for_cls(instance.__class__),
227 instance.logging_name,
228 )
229 else:
230 name = _qual_logger_name_for_cls(instance.__class__)
232 instance._echo = echoflag # type: ignore[misc]
234 logger: Union[logging.Logger, InstanceLogger]
236 if echoflag in (False, None):
237 # if no echo setting or False, return a Logger directly,
238 # avoiding overhead of filtering
239 logger = logging.getLogger(name)
240 else:
241 # if a specified echo flag, return an EchoLogger,
242 # which checks the flag, overrides normal log
243 # levels by calling logger._log()
244 logger = InstanceLogger(echoflag, name)
246 instance.logger = logger # type: ignore[misc]
249class echo_property:
250 __doc__ = """\
251 When ``True``, enable log output for this element.
253 This has the effect of setting the Python logging level for the namespace
254 of this element's class and object reference. A value of boolean ``True``
255 indicates that the loglevel ``logging.INFO`` will be set for the logger,
256 whereas the string value ``debug`` will set the loglevel to
257 ``logging.DEBUG``.
258 """
260 @overload
261 def __get__(
262 self, instance: Literal[None], owner: Type[Identified]
263 ) -> echo_property: ...
265 @overload
266 def __get__(
267 self, instance: Identified, owner: Type[Identified]
268 ) -> _EchoFlagType: ...
270 def __get__(
271 self, instance: Optional[Identified], owner: Type[Identified]
272 ) -> Union[echo_property, _EchoFlagType]:
273 if instance is None:
274 return self
275 else:
276 return instance._echo
278 def __set__(self, instance: Identified, value: _EchoFlagType) -> None:
279 instance_logger(instance, echoflag=value)