lkj.loggers#
Utils for logging.
Functions
|
Conditional log |
|
|
|
Check if the log flag is set to True in the instance. |
|
Decorator that adds logging before and after the function's call. |
|
Prints with a timestamp and optional refresh. |
|
Prints with a timestamp and optional refresh. |
|
Decorator that returns traceback and local variables on error. |
|
Prints a string with word-wrapping to a maximum line length, while preserving all existing newlines exactly as they appear. |
|
Prints a string or list ensuring the total line width does not exceed |
Classes
|
An extension of the suppress context manager that enables the user to issue a warning message when an import error occurs. |
|
- class lkj.loggers.CallOnError(*exceptions, on_error=<built-in function print>)[source]#
Bases:
suppressAn extension of the suppress context manager that enables the user to issue a warning message when an import error occurs.
>>> warn_about_import_errors = CallOnError(ImportError, on_error=lambda err: print(f"Warning: {err}")) >>> with warn_about_import_errors: ... import this_package_surely_does_not_exist Warning: No module named 'this_package_surely_does_not_exist' >>> with warn_about_import_errors: ... from os.this_module_does_not_exist import this_function_does_not_exist Warning: No module named 'os.this_module_does_not_exist'; 'os' is not a package
- lkj.loggers.clog(condition, *args, log_func=<built-in function print>, **kwargs)[source]#
Conditional log
>>> clog(False, "logging this") >>> clog(True, "logging this") logging this
One common usage is when there’s a verbose flag that allows the user to specify whether they want to log or not. Instead of having to litter your code with
if verbose:statements you can just do this:>>> verbose = True # say versbose is True >>> _clog = clog(verbose) # makes a clog with a fixed condition >>> _clog("logging this") logging this
You can also choose a different log function. Usually you’d want to use a logger object from the logging module, but for this example we’ll just use
printwith some modification:>>> _clog = clog(verbose, log_func=lambda x: print(f"hello {x}")) >>> _clog("logging this") hello logging this
- lkj.loggers.instance_flag_is_set(func, args, kwargs, flag_attr='verbose')[source]#
Check if the log flag is set to True in the instance.
- lkj.loggers.log_calls(func=None, *, logger=<built-in function print>, ingress_msg=<function _calling_name>, egress_msg=<function _done_calling_name>, func_name=operator.attrgetter('__name__'), log_condition=<function _always_log>)[source]#
Decorator that adds logging before and after the function’s call.
- Parameters:
logger (
Callable[[str],None]) – The logger function to use. Default is print.ingress_msg (
Callable[[str,tuple,dict],str]) – The message to log before calling the function. If it returns None, no message is logged. Default is “Calling {name}…” where name is the name of the function.egress_msg (
Callable[[str,tuple,dict,Any],str]) – The message to log after the function call. If it returns None, no message is logged. Default is “…. Done”.func_name (
Callable[[Callable],str]) – The function to use to get the function’s name.log_condition (
Callable[[Callable,tuple,dict],bool]) – The condition for logging. Should be a callable that takes a function, args, and kwargs, and return a bool. If log_condition returns False, no logging is done. Default is _always_log.
- Return type:
Tips:
Use `logger=print_with_timestamp` to get timestamps in your logs. Use `lambda *args: None` as the ingress_msg or egress_msg to suppress logging. Use `logger=lambda x: None` to suppress all logging.
- Return type:
- Returns:
The decorated function.
Example
>>> @log_calls ... def add(a, b): ... return a + b ... >>> add(2, 3) Calling add... .... Done calling add 5
>>> @log_calls( ... logger=lambda x: print(f"LOG: {x}"), ... ingress_msg=lambda name, *args: f"Start {name}!", ... egress_msg=lambda *args: "End" ... ) ... def multiply(a, b): ... return a * b ... >>> multiply(2, 3) LOG: Start multiply! LOG: End 6
Sometimes, you want to dynamically control whether to log or not. This is what the
log_conditionparameter is for. One common use case is to log only if a flag is set in an instance. Since this is a common use case, we provide thelog_calls.instance_flag_is_sethelper function for this. You can use partial to set the flag attribute:>>> import functools >>> log_if_verbose_set_to_true = functools.partial( ... log_calls.instance_flag_is_set, flag_attr='verbose' ... )
Now if you have a class with a
verboseattribute, you can use this helper function to log only ifverboseis set:>>> class MyClass: ... def __init__(self, verbose=False): ... self.verbose = verbose ... ... @log_calls(log_condition=log_if_verbose_set_to_true) ... def foo(self): ... print("Executing foo") ... >>> # Example usage >>> obj = MyClass(verbose=True) >>> obj.foo() # This will log Calling foo... Executing foo .... Done calling foo
But if verbose is set to
False, no logging will be done:>>> obj = MyClass(verbose=False) >>> obj.foo() # This will not log Executing foo
- lkj.loggers.print_progress(msg, *, refresh=None, display_time=True, print_func=<built-in function print>)#
Prints with a timestamp and optional refresh.
input: message, and possibly args (to be placed in the message string, sprintf-style
output: Displays the time (HH:MM:SS), and the message
use: To be able to track processes (and the time they take)
- lkj.loggers.print_with_timestamp(msg, *, refresh=None, display_time=True, print_func=<built-in function print>)[source]#
Prints with a timestamp and optional refresh.
input: message, and possibly args (to be placed in the message string, sprintf-style
output: Displays the time (HH:MM:SS), and the message
use: To be able to track processes (and the time they take)
- lkj.loggers.return_error_info_on_error(func, *, caught_error_types=(<class 'Exception'>, ), error_info_processor=<function dflt_error_info_processor>)[source]#
Decorator that returns traceback and local variables on error.
This decorator is useful for debugging. It will catch any exceptions that occur in the decorated function, and return an ErrorInfo object with the traceback and local variables at the time of the error.
- Parameters:
Tip
To parametrize this decorator, you can use a functools.partial function.
Tip
You can have your error_info_processor persist the error info to a file or database, or send it to a logging service.
>>> @return_error_info_on_error ... def foo(x, y=2): ... return x / y ... >>> t = foo(1, 2) >>> assert t == 0.5 >>> t = foo(1, y=0) Exiting from foo with error: division by zero >>> if isinstance(t, ErrorInfo): ... assert isinstance(t.error, ZeroDivisionError) ... hasattr(t, 'traceback') ... assert t.locals['args'] == (1,) ... assert t.locals['kwargs'] == {'y': 0}
- lkj.loggers.wrap_text_with_exact_spacing(text, *, max_width=80, print_func=<built-in function print>, line_prefix='')[source]#
Prints a string with word-wrapping to a maximum line length, while preserving all existing newlines exactly as they appear.
Args
text (str): The text to wrap and print.
max_width (int): The maximum width of each line (default is 88).
- lkj.loggers.wrapped_print(items, sep=', ', max_width=80, *, print_func=<built-in function print>, line_prefix='')[source]#
Prints a string or list ensuring the total line width does not exceed
max_width.If adding a new item would exceed this width, it starts a new line.
- Parameters:
Example
>>> items = [ ... "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", ... "item9", "item10" ... ] >>> sep = ", " >>> wrapped_print(items, sep, max_width=30) item1, item2, item3, item4, item5, item6, item7, item8, item9, item10
>>> items = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] >>> sep = " - " >>> wrapped_print(items, sep, max_width=10) a - b - c - d - e - f - g - h - i - j
Note that you have control over the
print_func. This, for example, allows you to just return the string instead of printing it.>>> wrapped_print(items, sep, max_width=10, print_func=lambda x: x) 'a - b - c\n- d - e -\nf - g - h\n- i - j'