Skip to content

Commit 41934e9

Browse files
committed
Added Exception handeling to logwrap
1 parent 04869d1 commit 41934e9

File tree

2 files changed

+83
-34
lines changed

2 files changed

+83
-34
lines changed

classmods/_decorators.py

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,116 @@
22
from functools import wraps
33
from typing import Any, Callable, Literal, Optional, Tuple, TypeAlias
44

5-
LOG_LEVEL: TypeAlias = Literal['CRITICAL','ERROR','WARNING','INFO','DEBUG','NOTSET','DEFAULT']
5+
LOG_LEVEL: TypeAlias = Literal['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET', 'DEFAULT']
66

77
def logwrap(
8-
before: Optional[Tuple[LOG_LEVEL, str]] | str = None,
9-
after: Optional[Tuple[LOG_LEVEL, str]] | str = None,
10-
show_args: Optional[LOG_LEVEL] = None
11-
) -> Callable:
8+
before: Optional[Tuple[LOG_LEVEL, str]] | str | bool = None,
9+
on_exception: Optional[Tuple[LOG_LEVEL, str]] | str | bool = None,
10+
after: Optional[Tuple[LOG_LEVEL, str]] | str | bool = None,
11+
) -> Callable:
1212
"""
1313
Simple dynamic decorator to log function calls. Uses the logging module with your current project configurations.
14-
Use LOG_LEVEL literal for using standard log levels.
15-
**Warning**: Providing wrong log level won't raise any exception but will default to specified defaults with DEFAULT level.
14+
Use LOG_LEVEL literal for using standard log levels.
15+
The messages will get formated in proccess so you can use templating for better logging.
16+
17+
(`func`: Function Name, `args`: Arguments Tuple, `kwargs`: Keyword Arguments Dict, `e`: The Exception if Exists)
1618
19+
Passing bool will use default levels and messages.(
20+
Before: 'DEBUG - Calling {func} - args:{args}, kwargs:{kwargs}'
21+
After: 'INFO - Function {func} ended'
22+
On Exception: 'ERROR - Error on {func}: {e}')
23+
24+
**Warning**: If options are negative, it will not log the option.
25+
**Warning**: Providing wrong log level won't raise any exception but will default to specified defaults with DEFAULT level.
26+
1727
Args:
1828
before: Tuple of log level and message to log before function call or string with default of `DEBUG` level.
29+
on_exception: Tuple of log level and message to log exception of function call or string with default of `ERROR` level.
1930
after: Tuple of log level and message to log after function call or string with default of `INFO` level.
20-
show_args: Log level to use for logging function parameters before a call (eg. Args and kwargs) defaults to `DEBUG` level.
2131
2232
Examples:
2333
>>> # Example with Custom Levels
24-
>>> @logwrap(before=('INFO', 'Function starting'), after=('INFO', 'Function ended'), show_args='DEBUG')
34+
>>> @logwrap(before=('INFO', '{func} starting, args={args} kwargs={kwargs}'), after=('INFO', '{my_func} ended'))
2535
>>> def my_func(my_arg, my_kwarg=None):
2636
>>> ...
2737
>>> my_func('hello', my_kwarg=123) # calling the function
28-
Info - My Function is Starting
29-
Debug - Arguments: args=('hello',), kwargs={'my_kwarg': 123}
30-
Info - Function Ended
38+
Info - my_func Starting, args=('hello',), kwargs={'my_kwarg': 123}
39+
Info - my_func Ended
3140
>>> # Example with defaults.
32-
>>> @logwrap(before='Starting', after='Ended')
41+
>>> @logwrap(before=True, after=True)
3342
>>> def my_new_func():
3443
>>> ...
3544
>>> my_new_func()
36-
Debug - Starting
37-
Info - Ended
45+
Debug - Calling my_new_func - args:(), kwargs:{}
46+
Info - Function my_new_func ended
47+
>>> # Example with Exception
48+
>>> @logwrap(on_exception=True)
49+
>>> def error_func():
50+
>>> raise Exception('My exception msg')
51+
>>> error_func()
52+
Error - Error on error_func: My exception msg')
3853
"""
39-
if isinstance(before, str):
40-
before = ('DEFAULT', before)
54+
def normalize(
55+
default_level: LOG_LEVEL,
56+
default_msg: str,
57+
option: Optional[Tuple[LOG_LEVEL, str]] | str | bool | None,
58+
) -> Tuple[LOG_LEVEL, str] | None:
59+
"""
60+
Normalize the options to specified args and make the input to `Tuple[LOG_LEVEL, str] | None`.
61+
Returns None on negative inputs.
62+
63+
Args:
64+
default_level(LOG_LEVEL): default level on str, bool inputs
65+
default_msg(str): default msg on bool inputs
66+
option(Optional[Tuple[LOG_LEVEL, str]] | str | bool | None): The option to normalize
67+
68+
Returns:
69+
(Tuple[LOG_LEVEL, str] | None): Normalized output for logging wraper
70+
"""
71+
if isinstance(option, bool) and option is True:
72+
return (default_level, default_msg)
73+
74+
elif isinstance(option, str):
75+
return (default_level, option)
4176

42-
if isinstance(after, str):
43-
after = ('DEFAULT', after)
77+
elif isinstance(option, tuple):
78+
return option
4479

45-
def decorator(func):
80+
elif not option or option is None:
81+
return None
82+
83+
before = normalize('DEBUG', 'Calling {func} - args:{args}, kwargs:{kwargs}', before)
84+
after = normalize('INFO', 'Function {func} ended', after)
85+
on_exception = normalize('ERROR', 'Error on {func}: {e}', on_exception)
86+
87+
def decorator(func: Callable) -> Callable:
4688
@wraps(func)
47-
def wrapper(*args, **kwargs):
89+
def wrapper(*args, **kwargs) -> Any:
4890
logger = logging.getLogger(func.__module__)
91+
func_name = func.__name__
4992

50-
# Log before
51-
if before:
52-
level, message = before
53-
logger.log(getattr(logging, level, logging.DEBUG), message)
93+
fmt_context = {
94+
'func': func_name,
95+
'args': args,
96+
'kwargs': kwargs,
97+
}
5498

55-
# Log method parameters if requested
56-
if show_args:
57-
level = getattr(logging, show_args, logging.DEBUG)
58-
logger.log(level, f"Arguments: args={args}, kwargs={kwargs}")
99+
if before:
100+
level, msg = before
101+
logger.log(getattr(logging, level, logging.DEBUG), msg.format(**fmt_context))
59102

60-
result = func(*args, **kwargs)
103+
try:
104+
result = func(*args, **kwargs)
105+
except Exception as e:
106+
if on_exception:
107+
level, msg = on_exception
108+
fmt_context['e'] = e
109+
logger.log(getattr(logging, level, logging.ERROR), msg.format(**fmt_context))
110+
raise e
61111

62-
# Log after
63112
if after:
64-
level, message = after
65-
logger.log(getattr(logging, level, logging.INFO), message)
113+
level, msg = after
114+
logger.log(getattr(logging, level, logging.INFO), msg.format(**fmt_context))
66115

67116
return result
68117
return wrapper

test/test_logwrap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def my_new_func():
99

1010
def test_with_custom_leveling():
1111
# Example with Custom Levels
12-
@logwrap(before=('INFO', 'Function starting'), after=('INFO', 'Function ended'), show_args='DEBUG')
12+
@logwrap(before=('INFO', 'Function starting'), after=('INFO', 'Function ended'))
1313
def my_func(my_arg, my_kwarg=None):
1414
...
1515
my_func('hello', my_kwarg=123) # calling the function

0 commit comments

Comments
 (0)