mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

* adds log module * enable logging in the app * adds a getLogger method to AddonManager * change log level depending on ANKIDEV * fix undefined module variable * - fix addons log file path - remove a breakpoint leftover set the addons log files under pm.addonFolder()/NNNNNN/user_files/logs/NNNNNN.log * fix path bug * move log closing handling into AddonManager deleteAddon/backupUserFiles methods * logging module level import fix undefined variable in backupUserFiles * pretty format log records * move MediaServer log into logging * update CONTRIBUTORS * documentation cleanup * capture warnings into log messages fix waitress verbosity * remove record_factory function * add get_logger method alias to getLogger in AddonManager switch to TimedRotatingFileHandler handler fix minor typo * set main log level to DEBUG if ANKIDEV is not 0 (or unset) added two new methods to AddonManager addon_get_logger/addon_toggle_log_level * add new find_logger_output to AddonManager * move logs under pm.base * change log output * update addonmanager getlogger * Format imports * Refactor logging set-up slightly and tweak docstring * Remove obsolete log closing statements As logs are no longer stored in user_files, we do not need to close their handlers * Refactor and try to simplify log module * Remove demo code * Refactor and update add-on manager logging API * Simplify writing unit tests for add-ons that use logging Loggers are likely to be also employed in non UI code, so it seems like a good idea to decouple them from requiring a running Anki instance to work (thus freeing add-on authors from the need to mock Anki APIs in their tests). * Fix arguments and drop obsolete inline instructions Lets add a section on logging to the add-on docs instead * Drop unnecessary import * Supply logging basicConfig force option by default Until we change the module import order and thus ensure that `log` is always evaluated before third-party dependencies have a chance to initialize the root logger, `force` is non-optional. * Fix formatting and type errors * Restore mediasrv type ignore comments * Add note on prefix API stability * Consistently use addon_from_module in new code * Use logFolder rather than profileFolder * Adjust method name for PEP8 * Change loggerDict access path, satisfying pylint * Drop unused import and use lazy % formatting * lint fix * refactor .log_folder -> .addon_logs store anki.log under logdir * Fix method name (dae) * Disable file-based logging in the backend (dae) I have never found this useful, and it logs nothing by default, so creating/opening the file is a waste. Removing it also ensures that addon_logs() is solely used for add-ons. --------- Co-authored-by: Glutanimate <5459332+glutanimate@users.noreply.github.com>
101 lines
3.4 KiB
Python
101 lines
3.4 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import sys
|
|
from logging.handlers import TimedRotatingFileHandler
|
|
from pathlib import Path
|
|
from typing import Optional, cast
|
|
|
|
# All loggers with the following prefix will be treated as add-on loggers
|
|
#
|
|
# To instatiate a logger with this prefix, use aqt.AddonManager.get_logger()
|
|
#
|
|
# NOTE: Add-ons might also directly instantiate a logger with this prefix, e.g. in
|
|
# order to avoid depending on the Anki codebase, so this prefix should not
|
|
# be changed.
|
|
ADDON_LOGGER_PREFIX = "addon."
|
|
|
|
# Formatter used for all loggers
|
|
FORMATTER = logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")
|
|
|
|
|
|
class AnkiLoggerManager(logging.Manager):
|
|
# inspired by: https://github.com/abdnh/ankiutils/blob/master/src/ankiutils/log.py
|
|
|
|
def __init__(
|
|
self,
|
|
logs_path: Path | str,
|
|
existing_loggers: dict[str, logging.Logger | logging.PlaceHolder],
|
|
rootnode: logging.RootLogger,
|
|
):
|
|
super().__init__(rootnode)
|
|
self.loggerDict = existing_loggers
|
|
self.logs_path = Path(logs_path)
|
|
|
|
def getLogger(self, name: str) -> logging.Logger:
|
|
if not name.startswith(ADDON_LOGGER_PREFIX) or name in self.loggerDict:
|
|
return super().getLogger(name)
|
|
|
|
# Create a new add-on logger
|
|
logger = super().getLogger(name)
|
|
|
|
module = name.split(ADDON_LOGGER_PREFIX)[1].partition(".")[0]
|
|
path = get_addon_logs_folder(self.logs_path, module=module) / f"{module}.log"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Keep the last 10 days of logs
|
|
handler = TimedRotatingFileHandler(
|
|
filename=path, when="D", interval=1, backupCount=10, encoding="utf-8"
|
|
)
|
|
handler.setFormatter(FORMATTER)
|
|
|
|
logger.addHandler(handler)
|
|
|
|
return logger
|
|
|
|
|
|
def get_addon_logs_folder(logs_path: Path | str, module: str) -> Path:
|
|
return Path(logs_path) / "addons" / module
|
|
|
|
|
|
def find_addon_logger(module: str) -> logging.Logger | None:
|
|
return cast(
|
|
Optional[logging.Logger],
|
|
logging.Logger.manager.loggerDict.get(f"{ADDON_LOGGER_PREFIX}{module}"),
|
|
)
|
|
|
|
|
|
def setup_logging(path: Path | str, **kwargs) -> None:
|
|
"""
|
|
Set up logging for the application.
|
|
|
|
Configures the root logger to output logs to stdout by default, with custom
|
|
handling for add-on logs. The add-on logs are saved to a separate folder and file
|
|
for each add-on, under the path provided.
|
|
|
|
Args:
|
|
path (Path): The path where the log files should be stored.
|
|
**kwargs: Arbitrary keyword arguments for logging.basicConfig
|
|
"""
|
|
|
|
# Patch root logger manager to handle add-on loggers
|
|
logger_manager = AnkiLoggerManager(
|
|
path, existing_loggers=logging.Logger.manager.loggerDict, rootnode=logging.root
|
|
)
|
|
logging.Logger.manager = logger_manager
|
|
|
|
stdout_handler = logging.StreamHandler(stream=sys.stdout)
|
|
stdout_handler.setFormatter(FORMATTER)
|
|
logging.basicConfig(handlers=[stdout_handler], force=True, **kwargs)
|
|
logging.captureWarnings(True)
|
|
|
|
# Silence some loggers of external libraries:
|
|
silenced_loggers = [
|
|
"waitress.queue",
|
|
]
|
|
for logger in silenced_loggers:
|
|
logging.getLogger(logger).setLevel(logging.CRITICAL)
|
|
logging.getLogger(logger).propagate = False
|