├── .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 | 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) --------------------------------------------------------------------------------