├── .gitignore
├── log-decorators
├── .idea
│ ├── .gitignore
│ ├── misc.xml
│ ├── vcs.xml
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── modules.xml
│ └── log-decorators.iml
├── __pycache__
│ ├── log.cpython-38.pyc
│ └── log_decorator.cpython-38.pyc
├── calculator.py
├── log.py
└── log_decorator.py
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | venv/
--------------------------------------------------------------------------------
/log-decorators/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # log-decorator
2 | Best practices and methods to implement logging in python project
3 |
--------------------------------------------------------------------------------
/log-decorators/__pycache__/log.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hima03/log-decorator/HEAD/log-decorators/__pycache__/log.cpython-38.pyc
--------------------------------------------------------------------------------
/log-decorators/__pycache__/log_decorator.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hima03/log-decorator/HEAD/log-decorators/__pycache__/log_decorator.cpython-38.pyc
--------------------------------------------------------------------------------
/log-decorators/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/log-decorators/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/log-decorators/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/log-decorators/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/log-decorators/.idea/log-decorators.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/log-decorators/calculator.py:
--------------------------------------------------------------------------------
1 | import log_decorator
2 | import log
3 |
4 | class Calculator():
5 | def __init__(self, first=0, second=0, log_file_name='', log_file_dir=''):
6 | self.first = first
7 | self.second = second
8 | #log file name and directory which we want to keep
9 | self.log_file_name = log_file_name
10 | self.log_file_dir = log_file_dir
11 | # Initializing logger object to write custom logs
12 | self.logger_obj = log.get_logger(log_file_name=self.log_file_name, log_sub_dir=self.log_file_dir)
13 |
14 | @log_decorator.log_decorator()
15 | def add(self, third=0, fourth=0):
16 | # writing custom logs specific to function, outside of log decorator, if needed
17 | self.logger_obj.info("Add function custom log, outside decorator")
18 | try:
19 | return self.first + self.second + third + fourth
20 | except:
21 | raise
22 |
23 | @log_decorator.log_decorator()
24 | def divide(self):
25 | self.logger_obj.info("Divide function custom log, outside decorator")
26 | try:
27 | return self.first / self.second
28 | except:
29 | raise
30 |
31 | if __name__ == '__main__':
32 | calculator = Calculator(5, 0, 'calculator_file', 'calculator_dir')
33 | calculator.add(third=2,fourth=3)
34 | calculator.divide()
35 |
--------------------------------------------------------------------------------
/log-decorators/log.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 |
5 | class CustomFormatter(logging.Formatter):
6 | """ Custom Formatter does these 2 things:
7 | 1. Overrides 'funcName' with the value of 'func_name_override', if it exists.
8 | 2. Overrides 'filename' with the value of 'file_name_override', if it exists.
9 | """
10 |
11 | def format(self, record):
12 | if hasattr(record, 'func_name_override'):
13 | record.funcName = record.func_name_override
14 | if hasattr(record, 'file_name_override'):
15 | record.filename = record.file_name_override
16 | return super(CustomFormatter, self).format(record)
17 |
18 |
19 | def get_logger(log_file_name, log_sub_dir=""):
20 | """ Creates a Log File and returns Logger object """
21 |
22 | windows_log_dir = 'c:\\logs_dir\\'
23 | linux_log_dir = '/logs_dir/'
24 |
25 | # Build Log file directory, based on the OS and supplied input
26 | log_dir = windows_log_dir if os.name == 'nt' else linux_log_dir
27 | log_dir = os.path.join(log_dir, log_sub_dir)
28 |
29 | # Create Log file directory if not exists
30 | if not os.path.exists(log_dir):
31 | os.makedirs(log_dir)
32 |
33 | # Build Log File Full Path
34 | logPath = log_file_name if os.path.exists(log_file_name) else os.path.join(log_dir, (str(log_file_name) + '.log'))
35 |
36 | # Create logger object and set the format for logging and other attributes
37 | logger = logging.Logger(log_file_name)
38 | logger.setLevel(logging.DEBUG)
39 | handler = logging.FileHandler(logPath, 'a+')
40 | """ Set the formatter of 'CustomFormatter' type as we need to log base function name and base file name """
41 | handler.setFormatter(CustomFormatter('%(asctime)s - %(levelname)-10s - %(filename)s - %(funcName)s - %(message)s'))
42 | logger.addHandler(handler)
43 |
44 | # Return logger object
45 | return logger
46 |
--------------------------------------------------------------------------------
/log-decorators/log_decorator.py:
--------------------------------------------------------------------------------
1 | import sys, os, functools
2 | from inspect import getframeinfo, stack
3 | import log
4 |
5 | def log_decorator(_func=None):
6 | def log_decorator_info(func):
7 | @functools.wraps(func)
8 | def log_decorator_wrapper(self, *args, **kwargs):
9 | # Build logger object
10 | logger_obj = log.get_logger(log_file_name=self.log_file_name, log_sub_dir=self.log_file_dir)
11 |
12 | """ Create a list of the positional arguments passed to function.
13 | - Using repr() for string representation for each argument. repr() is similar to str() only difference being
14 | it prints with a pair of quotes and if we calculate a value we get more precise value than str(). """
15 | args_passed_in_function = [repr(a) for a in args]
16 | """ Create a list of the keyword arguments. The f-string formats each argument as key=value, where the !r
17 | specifier means that repr() is used to represent the value. """
18 | kwargs_passed_in_function = [f"{k}={v!r}" for k, v in kwargs.items()]
19 |
20 | """ The lists of positional and keyword arguments is joined together to form final string """
21 | formatted_arguments = ", ".join(args_passed_in_function + kwargs_passed_in_function)
22 |
23 | """ Generate file name and function name for calling function. __func.name__ will give the name of the
24 | caller function ie. wrapper_log_info and caller file name ie log-decorator.py
25 | - In order to get actual function and file name we will use 'extra' parameter.
26 | - To get the file name we are using in-built module inspect.getframeinfo which returns calling file name """
27 | py_file_caller = getframeinfo(stack()[1][0])
28 | extra_args = { 'func_name_override': func.__name__,
29 | 'file_name_override': os.path.basename(py_file_caller.filename) }
30 |
31 | """ Before to the function execution, log function details."""
32 | logger_obj.info(f"Arguments: {formatted_arguments} - Begin function")
33 | try:
34 | """ log return value from the function """
35 | value = func(self, *args, **kwargs)
36 | logger_obj.info(f"Returned: - End function {value!r}")
37 | except:
38 | """log exception if occurs in function"""
39 | logger_obj.error(f"Exception: {str(sys.exc_info()[1])}")
40 | raise
41 | # Return function value
42 | return value
43 | # Return the pointer to the function
44 | return log_decorator_wrapper
45 | # Decorator was called with arguments, so return a decorator function that can read and return a function
46 | if _func is None:
47 | return log_decorator_info
48 | # Decorator was called without arguments, so apply the decorator to the function immediately
49 | else:
50 | return log_decorator_info(_func)
--------------------------------------------------------------------------------