Source code for pywikibot.logging

"""User output/logging functions.

Six output functions are defined. Each requires a ``msg`` argument
All of these functions generate a message to the log file if
logging is enabled (`-log` or `-debug` command line arguments).

The functions :func:`info` (alias :func:`output`), :func:`stdout`,
:func:`warning` and :func:`error` all display a message to the user
through the logger object; the only difference is the priority level,
which can be used by the application layer to alter the display. The
:func:`stdout` function should be used only for data that is the
"result" of a script, as opposed to information messages to the user.

The function :func:`log` by default does not display a message to the
user, but this can be altered by using the `-verbose` command line
option.

The function :func:`debug` only logs its messages, they are never
displayed on the user console. :func:`debug()` takes a required second
argument, which is a string indicating the debugging layer.

.. seealso::
   - :pyhow:`Logging HOWTO<logging>`
   - :python:`Logging Cookbook<howto/logging-cookbook.html>`
"""
#
# (C) Pywikibot team, 2010-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations


__all__ = (
    'CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'WARNING', 'STDOUT', 'VERBOSE',
    'INPUT',
    'add_init_routine',
    'logoutput', 'info', 'output', 'stdout', 'warning', 'error', 'log',
    'critical', 'debug', 'exception',
)

import logging
import os
import sys

# logging levels
from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING
from typing import Any

from pywikibot.backports import Callable
from pywikibot.tools import deprecated_args, issue_deprecation_warning


STDOUT = 16  #:
VERBOSE = 18  #:
INPUT = 25  #:
"""Three additional logging levels which are implemented beside
:const:`CRITICAL`, :const:`DEBUG`, :const:`ERROR`, :const:`INFO` and
:const:`WARNING`.

.. seealso:: :python:`Python Logging Levels<logging.html#logging-levels>`
"""

_init_routines: list[Callable[[], Any]] = []
_inited_routines = set()


[docs] def add_init_routine(routine: Callable[[], Any]) -> None: """Add a routine to be run as soon as possible.""" _init_routines.append(routine)
def _init() -> None: """Init any routines which have not already been called.""" for init_routine in _init_routines: found = init_routine in _inited_routines # prevent infinite loop _inited_routines.add(init_routine) if not found: init_routine() # Clear the list of routines to be inited _init_routines[:] = [] # the global variable is used with slice operator # Note: The frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def logoutput(msg: Any, *args: Any, level: int = INFO, **kwargs: Any) -> None: """Format output and send to the logging module. Dispatch helper function used by all the user-output convenience functions. It can be used to implement your own high-level output function with a different logging level. `msg` can contain special sequences to create colored output. These consist of the color name in angle bracket, e. g. <<lightpurple>>. <<default>> resets the color. Other keyword arguments are passed unchanged to the logger; so far, the only argument that is useful is ``exc_info=True``, which causes the log message to include an exception traceback. .. versionchanged:: 7.2 Positional arguments for *decoder* and *newline* are deprecated; keyword arguments should be used. :param msg: The message to be printed. :param args: Not used yet; prevents positional arguments except `msg`. :param level: The logging level; supported by :func:`logoutput` only. :keyword bool newline: If newline is True (default), a line feed will be added after printing the msg. :keyword str layer: Suffix of the logger name separated by dot. By default no suffix is used. :keyword str decoder: If msg is bytes, this decoder is used to decode. Default is 'utf-8', fallback is 'iso8859-1' :param kwargs: For the other keyword arguments refer :python:`Logger.debug()<library/logging.html#logging.Logger.debug>` """ # invoke any init routines if _init_routines: _init() keys: tuple[str, ...] # cleanup positional args if level == ERROR: keys = ('decoder', 'newline', 'exc_info') elif level == DEBUG: keys = ('layer', 'decoder', 'newline') else: keys = ('decoder', 'newline') for i, arg in enumerate(args): key = keys[i] issue_deprecation_warning( f'Positional argument {i + 1} ({arg})', f'keyword argument "{key}={arg}"', since='7.2.0') if key in kwargs: warning(f'{key!r} is given as keyword argument {arg!r} already; ' f'ignoring {kwargs[key]!r}') else: kwargs[key] = arg # frame 0 is logoutput() in this module, # frame 1 is the deprecation wrapper of this function # frame 2 is the convenience function (output(), etc.) # frame 3 is the deprecation wrapper the convenience function # frame 4 is whatever called the convenience function newline = kwargs.pop('newline', True) frame = sys._getframe(4) module = os.path.basename(frame.f_code.co_filename) context = {'caller_name': frame.f_code.co_name, 'caller_file': module, 'caller_line': frame.f_lineno, 'newline': ('\n' if newline else '')} context.update(kwargs.pop('extra', {})) decoder = kwargs.pop('decoder', 'utf-8') if isinstance(msg, bytes): try: msg = msg.decode(decoder) except UnicodeDecodeError: msg = msg.decode('iso8859-1') layer = kwargs.pop('layer', '') logger = logging.getLogger(('pywiki.' + layer).strip('.')) logger.log(level, msg, extra=context, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def info(msg: Any = '', *args: Any, **kwargs: Any) -> None: """Output a message to the user with level :const:`INFO`. ``msg`` will be sent to stderr via :mod:`pywikibot.userinterfaces`. It may be omitted and a newline is printed in that case. The arguments are interpreted as for :func:`logoutput`. .. versionadded:: 7.2 was renamed from :func:`output`. Positional arguments for *decoder* and *newline* are deprecated; keyword arguments should be used. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.info()<library/logging.html#logging.Logger.info>` """ logoutput(msg, *args, **kwargs)
output = info """Synonym for :func:`info` for backward compatibility. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 was renamed to :func:`info`; `text` was renamed to `msg`; `msg` paramerer may be omitted; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.info()<library/logging.html#logging.Logger.info>` """ # Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def stdout(msg: Any = '', *args: Any, **kwargs: Any) -> None: """Output script results to the user with level :const:`STDOUT`. ``msg`` will be sent to standard output (stdout) via :mod:`pywikibot.userinterfaces`, so that it can be piped to another process. All other functions will send to stderr. `msg` may be omitted and a newline is printed in that case. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `text` was renamed to `msg`; `msg` parameter may be omitted; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: - :python:`Logger.log()<library/logging.html#logging.Logger.log>` - :wiki:`Pipeline (Unix)` """ logoutput(msg, *args, level=STDOUT, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def warning(msg: Any, *args: Any, **kwargs: Any) -> None: """Output a warning message to the user with level :const:`WARNING`. ``msg`` will be sent to stderr via :mod:`pywikibot.userinterfaces`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `text` was renamed to `msg`; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.warning()<library/logging.html#logging.Logger.warning>` """ logoutput(msg, *args, level=WARNING, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def error(msg: Any, *args: Any, **kwargs: Any) -> None: """Output an error message to the user with level :const:`ERROR`. ``msg`` will be sent to stderr via :mod:`pywikibot.userinterfaces`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `text` was renamed to `msg`; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.error()<library/logging.html#logging.Logger.error>` """ logoutput(msg, *args, level=ERROR, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def log(msg: Any, *args: Any, **kwargs: Any) -> None: """Output a record to the log file with level :const:`VERBOSE`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `text` was renamed to `msg`; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.log()<library/logging.html#logging.Logger.log>` """ logoutput(msg, *args, level=VERBOSE, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def critical(msg: Any, *args: Any, **kwargs: Any) -> None: """Output a critical record to the user with level :const:`CRITICAL`. ``msg`` will be sent to stderr via :mod:`pywikibot.userinterfaces`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `text` was renamed to `msg`; only keyword arguments are allowed except for `msg`. Keyword parameter *layer* was added. .. seealso:: :python:`Logger.critical() <library/logging.html#logging.Logger.critical>` """ logoutput(msg, *args, level=CRITICAL, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(text='msg') # since 7.2 def debug(msg: Any, *args: Any, **kwargs: Any) -> None: """Output a debug record to the log file with level :const:`DEBUG`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 `layer` parameter is optional; `text` was renamed to `msg`; only keyword arguments are allowed except for `msg`. .. seealso:: :python:`Logger.debug()<library/logging.html#logging.Logger.debug>` """ logoutput(msg, *args, level=DEBUG, **kwargs)
# Note: The logoutput frame must be updated if this decorator is removed
[docs] @deprecated_args(tb='exc_info') # since 7.2 def exception(msg: Any = None, *args: Any, exc_info: bool = True, **kwargs: Any) -> None: """Output an error traceback to the user with level :const:`ERROR`. Use directly after an 'except' statement:: ... except Exception: pywikibot.exception() ... or alternatively:: ... except Exception as e: pywikibot.exception(e) ... With `exc_info=False` this function works like :func:`error` except that the `msg` parameter may be omitted. This function should only be called from an Exception handler. ``msg`` will be sent to stderr via :mod:`pywikibot.userinterfaces`. The arguments are interpreted as for :func:`logoutput`. .. versionchanged:: 7.2 only keyword arguments are allowed except for `msg`; `exc_info` keyword is to be used instead of `tb`. Keyword parameter *layer* was added. .. versionchanged:: 7.3 `exc_info` is True by default .. seealso:: :python:`Logger.exception() <library/logging.html#logging.Logger.exception>` The arguments are interpreted as for :meth:`output`. """ if msg is None: exc_type, value, _tb = sys.exc_info() msg = str(value) if not exc_info: msg += f' ({exc_type.__name__})' assert msg is not None error(msg, *args, exc_info=exc_info, **kwargs)