|
| 1 | +import functools |
| 2 | +import logging |
| 3 | +import json |
| 4 | +import time |
| 5 | + |
| 6 | +import requests |
| 7 | + |
| 8 | +from . import client |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +def ava_log(func): |
| 13 | + """ |
| 14 | + Function decorator for implementation of logging |
| 15 | + @param func: function where logging needs to be implemented |
| 16 | + @return: function |
| 17 | + """ |
| 18 | + |
| 19 | + @functools.wraps(func) |
| 20 | + def wrapper(*args, **kwargs): |
| 21 | + logger = logging.getLogger() |
| 22 | + is_log_req_resp_allowed = False |
| 23 | + ava_log_entry = {} |
| 24 | + is_error_log = False |
| 25 | + |
| 26 | + # Logic : |
| 27 | + # 1) Check if decorator is called for function in AvaTaxClient class |
| 28 | + # 2) If logger is set then use it else use new instance of logger (this property is useful when SDK consumer |
| 29 | + # wants to use specific configuration) |
| 30 | + # 3) Execute actual method and create log entry in case of http response only |
| 31 | + # 4) In case of exception though log error |
| 32 | + execution_start_time = time.perf_counter() |
| 33 | + try: |
| 34 | + if isinstance(args[0], client.AvataxClient): |
| 35 | + if hasattr(args[0], "logger") and (args[0].__getattribute__("logger") is not None): |
| 36 | + logger = args[0].__getattribute__("logger") |
| 37 | + if hasattr(args[0], "is_log_req_resp_allowed"): |
| 38 | + is_log_req_resp_allowed = args[0].__getattribute__("is_log_req_resp_allowed") |
| 39 | + result = func(*args, **kwargs) |
| 40 | + if result is not None and isinstance(result, requests.models.Response): |
| 41 | + ava_log_entry = get_ava_log_entry(result, is_log_req_resp_allowed) |
| 42 | + return result |
| 43 | + except Exception as e: |
| 44 | + is_error_log = True |
| 45 | + ava_log_entry["error"] = str(e) |
| 46 | + raise e |
| 47 | + finally: |
| 48 | + total_execution_time = time.perf_counter() - execution_start_time |
| 49 | + if ava_log_entry["execution_time"] is None: |
| 50 | + ava_log_entry["execution_time"] = total_execution_time * 1000 |
| 51 | + json_data = json.dumps(ava_log_entry, indent=4) |
| 52 | + if is_error_log: |
| 53 | + logger.error(json_data) |
| 54 | + else: |
| 55 | + logger.info(json_data) |
| 56 | + |
| 57 | + return wrapper |
| 58 | + |
| 59 | + |
| 60 | +def get_ava_log_entry(result: requests.Response, is_log_req_resp_allowed: bool) -> dict: |
| 61 | + log_entry = {} |
| 62 | + log_entry["execution_time"] = result.elapsed.total_seconds() * 1000 |
| 63 | + log_entry["x-correlation-id"] = result.headers["x-correlation-id"] |
| 64 | + log_entry["status_code"] = result.status_code |
| 65 | + log_entry["request_url"] = result.url |
| 66 | + log_entry["method"] = result.request.method |
| 67 | + if is_log_req_resp_allowed: |
| 68 | + log_entry["request"] = str(result.request.body) |
| 69 | + log_entry["response"] = result.text |
| 70 | + |
| 71 | + return log_entry |
| 72 | + |
| 73 | + |
| 74 | +def decorate_all_methods(decorator, exclude=["__init__", "add_credentials"]): |
| 75 | + """ |
| 76 | + Decorator to be applied at class level. This decorator decorates all methods in class with |
| 77 | + supplied decorator in parameter. |
| 78 | + @param decorator: decorator to be applied to all methods in class |
| 79 | + @param exclude: list of methods to exclude from being decorated. |
| 80 | + @return: class with decorated methods |
| 81 | + """ |
| 82 | + |
| 83 | + def decorate(cls): |
| 84 | + for attr in cls.__dict__: |
| 85 | + if callable(getattr(cls, attr)) and attr not in exclude: |
| 86 | + setattr(cls, attr, decorator(getattr(cls, attr))) |
| 87 | + return cls |
| 88 | + |
| 89 | + return decorate |
0 commit comments