"""
.. image:: https://img.shields.io/pypi/pyversions/mylogging.svg
:target: https://pypi.python.org/pypi/mylogging/
:alt: Python versions
.. image:: https://badge.fury.io/py/mylogging.svg
:target: https://badge.fury.io/py/mylogging
:alt: PyPI version
.. image:: https://img.shields.io/lgtm/grade/python/github/Malachov/mylogging.svg
:target: https://lgtm.com/projects/g/Malachov/mylogging/context:python
:alt: Language grade: Python
.. image:: https://readthedocs.org/projects/mylogging/badge/?version=latest
:target: https://mylogging.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://img.shields.io/badge/License-MIT-yellow.svg
:target: https://opensource.org/licenses/MIT
:alt: License: MIT
.. image:: https://codecov.io/gh/Malachov/mylogging/branch/master/graph/badge.svg
:target: https://codecov.io/gh/Malachov/mylogging
:alt: Codecov
My python warn-logging module. It log to console or to file based on configuration.
1) It's automatically colorized and formatted to be more readable and noticeable (you can immediately see what errors are yours)
2) It's possible to control logs and warnings behaviour (ignore, once, always) as in warnings.
3) It's possible to filter messages by level (INFO, DEBUG, WARNING, ERROR, CRITICAL)
Motivation for this project is to be able to have one very simple code base for logging and warning at once.
You can use one code for logging apps running on server (developers see what happens on server) and the same
code for printing info and warnings from developed library.
Links
=====
Official documentation - https://mylogging.readthedocs.io/
Official repo - https://github.com/Malachov/mylogging
Installation
============
Python >=3.6 (Python 2 is not supported).
Install just with::
pip install mylogging
Output
======
This is how the results of examples below look like in console.
.. image:: /_static/logging.png
:width: 620
:alt: Logging
For log file, just open example.log in your IDE.
This is how the results in log file opened in VS Code look like.
.. image:: /_static/logging_file.png
:width: 620
:alt: Logging into file
Examples:
=========
Library is made to be as simple as possible, so configuration should be easy (you don't need
to configure anything actually)... Just setup path to log file (will be created if not exists).
If you will not setup it, log to console will be used.
Change FILTER (defaults to once) and LEVEL (defaults to WARNING) if you need.
Then syntax is same as in logging module. Functions debug, info, warn, error and critical are available
and one extra traceback, where you set level as input parameter.
>>> import mylogging
>>> mylogging.config.TO_FILE = "log.log" # False for logging to console. It can be also full path (also pathlib.Path object)
...
>>> mylogging.warn("Hessian matrix copmputation failed for example")
...
>>> try:
... print(10 / 0)
... except ZeroDivisionError:
... mylogging.traceback("Maybe try to use something different than 0.", level="ERROR")
...
>>> mylogging.info("I am interesting info")
...
>>> mylogging.critical("This is fatal", caption="You can use captions as well")
There are also another functions you can use: `return_str` will return edited string (Color, indent and around signs).
Use case for that is mostly raising your errors. You can see in one second, whether raise is yours or from imported library.
>>> raise ModuleNotFoundError(mylogging.return_str("Try pip install...", caption="Library not installed error"))
Traceback (most recent call last):
...
ModuleNotFoundError:...
Another function is for ignoring specified warnings from imported libraries. Global warnings
settings are edited, so if you use it in some library that other users will use, don't forget to
reset user settings after end of your call with reset_outer_warnings_filter() or use it in
witch.catch_warnings(): block.
Sometimes only message does not work, then ignore it with class and warning type
>>> ignored_warnings = ["mean of empty slice"]
>>> ignored_warnings_class_type = [
... ("TestError", FutureWarning),
... ]
...
>>> mylogging.outer_warnings_filter(ignored_warnings, ignored_warnings_class_type)
...
>>> mylogging.reset_outer_warnings_filter()
If somebody is curious how it looks like on light color theme, here it goes...
.. image:: /_static/logging_white.png
:width: 620
:alt: Logging into file
Config
======
Some config, that can be configured globally for not having to use in each function call.
Config values has docstrings, so description should be visible in IDE help.
`OUTPUT` - Whether log to file or to console. 'console' or path to file (string or pathlib.Path).
Defaults by "console"
``LEVEL`` -
```FILTER``` - If the same logs, print it always, once or turn all logging off
Usually thats everything you will setup. If you need different formatting of output, you can define
FORMATTER_CONSOLE_STR or FORMATTER_FILE_STR with for example::
"{asctime} {levelname} " + "{filename}:{lineno}" + "{message}"
Rest options should be OK by default, but it's all up to you of course: You can setup for example
AROUND - Whether separate logs with line breaks and ==== or shring to save space. Defaults to True.
COLORIZE - Possible options: [True, False, 'auto']. Colorize is automated. If to console, it is
colorized, if to file, it's not (.log files can be colorized by IDE). Defaults to 'auto'.
"""
import traceback as trcbck
import os
import textwrap
import sys
import warnings
from ._config import config
from . import _misc
from .colors import colorize, colorize_code
from . import colors
__all__ = ["traceback", "warn", "return_str", "config"]
__version__ = "2.0.5"
__author__ = "Daniel Malachov"
__license__ = "MIT"
__email__ = "malachovd@seznam.cz"
# To enable colors in cmd...
os.system("")
def debug(message, caption=""):
"""Log debug info. Only difference with info is filtering LEVEL in config.
Args:
message (str): Message to be logged.
caption (str, optional): Headning of warning. Defaults to 'User message'.
"""
if not _misc.filter_out((caption + message)[:150], "DEBUG"):
_misc.log_warn(return_str(message, caption=caption, objectize=False, level="DEBUG"), level="DEBUG")
def info(message, caption=""):
"""Log info.
Args:
message (str): Message to be logged.
caption (str, optional): Headning of warning. Defaults to 'User message'.
"""
if not _misc.filter_out((caption + message)[:150], "INFO"):
_misc.log_warn(return_str(message, caption=caption, objectize=False, level="INFO"), level="INFO")
[docs]def warn(message, caption=""):
"""Raise warning - just message, not traceback. Can be colorized. Display of warning is based on warning settings.
You can configure how to cope with warnings with function set_warnings with debug parameter. Instead of traceback_warning
this is not from catched error. It usually bring some information good to know.
Args:
message (str): Any string content of warning.
caption (str, optional): Headning of warning. Defaults to 'User message'.
"""
if not _misc.filter_out((caption + message)[:150], "WARNING"):
_misc.log_warn(
return_str(message, caption=caption, objectize=False, level="WARNING"), level="WARNING"
)
def error(message, caption=""):
"""Same as warn, but can be filtered different way with level. This is only for loggin message.
If you want to log error code, you can use function traceback.
Args:
message (str): Any string content of error.
caption (str, optional): Headning of error. Defaults to 'User message'.
"""
if not _misc.filter_out((caption + message)[:150], "ERROR"):
_misc.log_warn(return_str(message, caption=caption, objectize=False, level="ERROR"), level="ERROR")
def critical(message, caption=""):
"""Same as warning, but usually describe error that stopped the application.
Args:
message (str): Any string content of error.
caption (str, optional): Headning of error. Defaults to 'User message'.
"""
if not _misc.filter_out((caption + message)[:150], "CRITICAL"):
_misc.log_warn(
return_str(message, caption=caption, objectize=False, level="CRITICAL"), level="CRITICAL"
)
# Just for naming convention
fatal = critical
[docs]def traceback(message=None, caption="error_type", level="ERROR", stack_level=3, remove_frame_by_line_str=[]):
"""Raise warning with current traceback as content. It means, that error was catched, but still something crashed.
Args:
message (str): Any string content of traceback.
caption (str, optional): Caption of warning. If 'error_type', than Error type (e.g. ZeroDivisionError) is used.
Defaults to 'error_type'.
"""
if remove_frame_by_line_str:
separated_traceback = get_traceback_with_removed_frames_by_line_string(remove_frame_by_line_str)
else:
separated_traceback = trcbck.format_exc()
compared_message = caption + message + separated_traceback
if _misc.filter_out(compared_message, level):
return
if caption == "error_type":
try:
caption = sys.exc_info()[1].__class__.__name__
except Exception:
caption = "Error"
if colors.USE_COLORS:
separated_traceback = colorize_code(separated_traceback)
separated_traceback = separated_traceback.rstrip()
separated_traceback = return_str(
message=message,
caption=caption,
objectize=False,
uncolored_message=f"\n\n{separated_traceback}",
level=level,
)
_misc.log_warn(separated_traceback, level=level, showwarning_details=False, stack_level=stack_level)
[docs]def return_str(
message,
caption="User message",
around="config",
objectize=True,
indent=4,
uncolored_message=None,
level="WARNING",
):
"""Return enhanced colored message. Used for raising exceptions, assertions.
Args:
message (str): Any string content of warning.
caption (ctr, optional): Headning of warning. Defaults to 'User message'.
around ((bool, str), optional): If print to file - whether print ====== lines around.
If 'auto', then if TO_FILE = True, then AROUND = False, if TO_FILE = False, AROUND = True.
If 'config', use global config (defaults 'auto'). Defaults to 'config'.
objectize (bool, optional): Turn into object (If call in raise - only way to print colors).
If you need string to variable, call str(). Defaults to True.
indent (int, optional): By how many spaces are logs indented (for better visibility). If 0,
than no indentation. Defaults to 4.
uncolored_message (str, optional): Appendix added to end that will not be colorized (or
already is colorized). Used for example for tracebacks. Defaults to None.
Returns:
str: Enhanced message as a string, that is wrapped by and can be colorized.
"""
# If only caption do not print None or False
if not message:
message = ""
if around == "config":
around = config._used_around
updated_str = colorize(message, level=level)
if uncolored_message:
if not around:
uncolored_message = uncolored_message + "\n"
updated_str = updated_str + uncolored_message
if around:
top_line = f"========= {caption} =========" if caption else "============================="
bottom_line = colorize(f"{'=' * len(top_line)}\n\n", level=level)
top_line = colorize(top_line, level=level)
updated_str = f"\n\n{top_line} \n\n{updated_str} \n\n{bottom_line}"
else:
if caption:
updated_str = f"{colorize(caption, level=level)}: {updated_str}"
if indent:
updated_str = textwrap.indent(text=updated_str, prefix=" " * indent)
if objectize:
updated_str = _misc.objectize_str(updated_str)
return updated_str
def outer_warnings_filter(messages=[], messages_and_categories=[]):
"""Also other libraries you use can raise warnings. This function can filter warnings from such a libraries.
Note:
!!! Globally overwrite warnings behaviour - even outside called module. If using in
library that other users use, you should reset their original filter settings with
reset_outer_warnings_filter() at the end of your code.
Args:
messages (list, optional): List of warnings (any part of inner string) that will be ignored even if debug is set.
Example ["AR coefficients are not stationary.", "Mean of empty slice",]. Defaults to [].
messages_and_categories (list, optional): List of tuples (string of module that raise it and warning type) that will be ignored even if debug is set.
Example [('statsmodels.tsa.arima_model', FutureWarning)]. Defaults to [].
"""
_misc.user_filters = warnings.filters.copy()
for i in messages:
warnings.filterwarnings("ignore", message=fr"[\s\S]*{i}*")
for i in messages_and_categories:
warnings.filterwarnings("ignore", module=i[0], category=i[1])
def reset_outer_warnings_filter():
"""If you are filtering warnings and log inside your library, it's good to keep user's filters."""
warnings.filters.clear()
warnings.filters.extend(_misc.user_filters)
def get_traceback_with_removed_frames_by_line_string(lines):
exc = trcbck.TracebackException(*sys.exc_info())
for i in exc.stack[:]:
if i.line in lines:
exc.stack.remove(i)
return "".join(exc.format())