Source code for apstools.utils.log_utils
"""
Support for logging
+++++++++++++++++++++++++++++++++++++++
There is a guide describing :ref:`howto_session_logs`.
.. autosummary::
~file_log_handler
~get_log_path
~setup_IPython_console_logging
~stream_log_handler
"""
import pathlib
from logging import DEBUG
from logging import FileHandler
from logging import Formatter
from logging import StreamHandler
from logging.handlers import RotatingFileHandler
[docs]def file_log_handler(
file_name_base,
maxBytes=0,
backupCount=0,
log_path=None,
level=None,
):
"""
Record logging output to a file.
PARAMETERS
file_name_base : *str*
Part of the name to store the log file. Full name is
``f"<log_path>/{file_name_base}.log"`` in present working directory.
log_path : *str*
Part of the name to store the log file. Full name is
``f"<log_path>/{file_name_base}.log"`` in present working directory.
default: (the present working directory)/LOG_DIR_BASE
level : *int*
Threshold for reporting messages with this logger. Logging messages
which are less severe than ``level`` will be ignored.
default: 10 (``logging.DEBUG`` or ``DEBUG``)
see: https://docs.python.org/3/library/logging.html#levels
maxBytes : (optional) *int*
Log file *rollover* begins whenever the current log file is nearly
*maxBytes* in length. A new file is written when the current line will
push the current file beyond this limit.
default: 0
backupCount : (optional) *int*
When *backupCount* is non-zero, the system will keep up to *backupCount*
numbered log files (with added extensions `.1`, '.2`, ...). The current
log file always has no numbered extension. The previous log file is the
one with the lowest extension number.
default: 0
.. note:: When either ``maxBytes`` or ``backupCount`` are zero,
log file rollover never occurs, so you generally want to set
``backupCount`` to at least 1, and have a non-zero ``maxBytes``.
"""
log_path = log_path or get_log_path()
log_file = log_path / f"{file_name_base}.log"
level = level or DEBUG
if maxBytes > 0 or backupCount > 0:
handler = RotatingFileHandler(log_file, maxBytes=maxBytes, backupCount=backupCount)
else:
handler = FileHandler(log_file)
handler.setLevel(level)
formatter = Formatter(
(
"|%(asctime)s"
"|%(levelname)s"
"|%(process)d"
"|%(name)s"
"|%(module)s"
"|%(lineno)d"
"|%(threadName)s"
"| - "
"%(message)s"
)
)
formatter.default_msec_format = "%s.%03d"
handler.setFormatter(formatter)
return handler
[docs]def get_log_path():
"""
Return a path to `./.logs`. Create directory if it does not exist.
"""
path = pathlib.Path().cwd() / ".logs"
if not path.exists():
path.mkdir()
return path
[docs]def setup_IPython_console_logging(logger=None, filename="ipython_console.log", log_path=None):
"""
Record all input (``In``) and output (``Out``) from IPython console.
PARAMETERS
logger
*object*:
Instance of ``logging.Logger``.
filename
*str*:
Name of the log file.
(default: ``ipython_console.log``)
log_path : *str*
Directory to store the log file. Full name is
``f"<log_path>/{file_name_base}.log"``.
default: (the present working directory)/LOG_DIR_BASE
"""
try:
from IPython import get_ipython
# start logging console to file
# https://ipython.org/ipython-doc/3/interactive/magics.html#magic-logstart
_ipython = get_ipython()
log_path = pathlib.Path(log_path or get_log_path())
io_file = log_path / filename
if _ipython is not None:
_ipython.magic(f"logstart -o -t {io_file} rotate")
if logger is not None:
logger.info("Console logging: %s", io_file)
except Exception as exc:
if logger is not None:
logger.exception("Could not setup console logging.")
else:
print(f"Could not setup console logging: {exc}")
[docs]def stream_log_handler(formatter=None, level="INFO"):
"""
Record logging output to a stream (such as the console).
PARAMETERS
formatter
*object*:
Instance of ``logging.Formatter``.
level
*str*:
Name of the logging level to report.
(default: ``INFO``)
"""
handler = StreamHandler()
# nice output format
# https://docs.python.org/3/library/logging.html#logrecord-attributes
if formatter is None:
# fmt: off
formatter = Formatter(
(
"%(levelname)-.1s" # only first letter
" %(asctime)s"
" - "
"%(message)s"
),
datefmt="%a-%H:%M:%S"
)
# fmt: on
formatter.default_msec_format = "%s.%03d"
handler.setFormatter(formatter)
handler.setLevel(level)
return handler
# -----------------------------------------------------------------------------
# :author: Pete R. Jemian
# :email: jemian@anl.gov
# :copyright: (c) 2017-2023, UChicago Argonne, LLC
#
# Distributed under the terms of the Argonne National Laboratory Open Source License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# -----------------------------------------------------------------------------