├── .env.staging ├── .gitignore ├── Dockerfile ├── Pipfile ├── README.md ├── db └── engine.py ├── main.py ├── middleware └── middleware.py ├── routers └── demo │ └── demo.py ├── start.sh └── utils ├── exception.py ├── helpers.py ├── invalid_response_class.py ├── logData.py ├── logging.py └── response_manipulator.py /.env.staging: -------------------------------------------------------------------------------- 1 | 2 | DB_IP = ' 3 | DB_PORT = '' 4 | DB_PASSWORD = '' 5 | DB_USER = '' 6 | DB_NAME = '' 7 | 8 | 9 | DSN ='' 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.pyc 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | .env.* 113 | 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | *.lock 134 | .vscode/launch.json 135 | 136 | 137 | nohup.out 138 | 139 | # python env 140 | pronto/bin/pip 141 | pronto/bin/pip3 142 | pronto/bin/pip3.8 143 | pronto/bin/pipenv 144 | pronto/bin/pipenv-resolver 145 | pronto/bin/python 146 | pronto/bin/python3 147 | pronto/bin/virtualenv 148 | pronto/bin/virtualenv-clone 149 | pronto/pyvenv.cfg 150 | pronto/bin/activate 151 | pronto/bin/activate.csh 152 | pronto/bin/activate.fish 153 | pronto/bin/Activate.ps1 154 | pronto/lib64 155 | 156 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.12-slim 2 | 3 | # Set environment varibles 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | WORKDIR /code/ 7 | # Install dependencies 8 | RUN pip install pipenv 9 | RUN pipenv install 10 | COPY Pipfile Pipfile.lock /code/ 11 | RUN pipenv install --system --dev 12 | COPY . /code/ 13 | 14 | 15 | CMD ["python","main.py"] 16 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | fastapi = {extras = ["all"], version = "*"} 8 | sqlalchemy = "*" 9 | pymysql = "*" 10 | uvicorn = {extras = ["standard"], version = "*"} 11 | uvloop = "*" 12 | dictalchemy3 = "==1.0.0" 13 | structlog = "*" 14 | loguru = "*" 15 | python-dotenv = "*" 16 | sentry-sdk = "*" 17 | 18 | [dev-packages] 19 | 20 | [requires] 21 | python_version = "3.8" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### **Fast-api-Boilerplate** 2 | 3 | FastAPI framework, high performance, easy to learn, fast to code, ready for production 4 | 5 | ## ENVIRONMENT 6 | For each Type of environment Create a respective file and pass those variables in **start.sh** file 7 | 8 | **Key** : ENVIRONMENT 9 | 10 | **Value** : [PRODUCTION,STAGING,DEVELOPMENT] (Any One) 11 | 12 | **File** : [.env.production, .env.staging, .env.development] (Respective to Value) 13 | 14 | 15 | 16 | ## Database Connection 17 | 18 | Add db_host, db_password, db_user, db_name , db_port in env file. 19 | 20 | Although using SQLAlchemy but that is just to create connection, Can implement models with respect to your needs. 21 | 22 | 23 | ## TO RUN PROJECT 24 | 25 | **Requirement** : Docker 26 | 27 | To run just use command "**bash start.sh**" on linux need to configure for other OS. 28 | 29 | Created Reponse, Logging Common Structure 30 | Added request_id(Can also be passed from Nginx Server) which can be helpful for debugging 31 | 32 | 33 | 34 | 35 | 36 | ## CONTRIBUTION 37 | Still Learning, 38 | 39 | So feel free, Anything You wanna contirubute. 40 | 41 | -------------------------------------------------------------------------------- /db/engine.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from sqlalchemy import create_engine, databases,MetaData,text 3 | from sqlalchemy.ext.declarative import declarative_base 4 | from sqlalchemy.orm import sessionmaker 5 | import os 6 | from dotenv import load_dotenv 7 | 8 | Base = declarative_base() 9 | 10 | path=os.getenv('ENVIRONMENT') 11 | 12 | if path: 13 | 14 | if path=='PRODUCTION': 15 | os.environ["ENVIRONMENT_PATH"] = '.env.production' 16 | elif path=='DEVELOPMENT': 17 | os.environ["ENVIRONMENT_PATH"] = '.env.development' 18 | elif path=='STAGING': 19 | os.environ["ENVIRONMENT_PATH"] = '.env.staging' 20 | else: 21 | raise RuntimeError('InCorrect Environment') 22 | 23 | file_path = os.environ["ENVIRONMENT_PATH"] 24 | isFile = os.path.isfile(file_path) 25 | if isFile==False: 26 | raise RuntimeError(file_path+' file not present') 27 | 28 | else: 29 | raise RuntimeError('Missing Environment') 30 | 31 | load_dotenv(dotenv_path=str(os.getenv('ENVIRONMENT_PATH'))) 32 | 33 | DB_USER=os.getenv("DB_USER") 34 | DB_PASSWORD=os.getenv("DB_PASSWORD") 35 | DB_IP=os.getenv("DB_IP") 36 | DB_NAME=os.getenv("DB_NAME") 37 | DB_PORT=os.getenv("DB_PORT") 38 | SQLALCHEMY_DATABASE_URL_DEV = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_IP}:{DB_PORT}/{DB_NAME}" 39 | 40 | ECHO=True 41 | if path=="PRODUCTION": 42 | ECHO=False 43 | 44 | db = create_engine( 45 | SQLALCHEMY_DATABASE_URL_DEV,echo=ECHO,json_serializer=True, pool_use_lifo=True, pool_pre_ping=True 46 | ) 47 | 48 | class DbExecute(object): 49 | def __init__(self) -> None: 50 | self.engine=db 51 | self.status=False 52 | self.data=[] 53 | 54 | def del_engine(self): 55 | del self.engine 56 | 57 | def fetchall(self,query,valuelist): 58 | result=db.execute(text(query),valuelist) 59 | self.del_engine() 60 | result=result.mappings().all() 61 | self.data=result if result else [] 62 | if len(self.data)>0: 63 | for index,row in enumerate(self.data): 64 | self.data[index]={column: str(getattr(row, column)) if isinstance(getattr(row, column), datetime.datetime) else getattr(row, column) for column in row._keymap.keys()} 65 | self.status=True 66 | return self 67 | else: 68 | return self 69 | 70 | def fetchone(self,query,valuelist): 71 | result=db.execute(text(query),valuelist) 72 | self.del_engine() 73 | result=result.mappings().fetchone() 74 | self.data=result if result else {} 75 | if self.data: 76 | 77 | self.data={column: str(getattr(self.data, column)) if isinstance(getattr(self.data, column), datetime.datetime) else getattr(self.data, column) for column in self.data._keymap.keys()} 78 | self.status=True 79 | return self 80 | else: 81 | return self 82 | 83 | # Function to update fields in a table. 84 | def update(self,query,valuelist): 85 | result=db.execute(text(query),valuelist) 86 | self.del_engine() 87 | self.status=True 88 | self.rows_effected = result.rowcount 89 | return self 90 | 91 | # Function to insert single or multiple rows in a table. For single row-entry valueList should be dictonary and for multiple rows-entry valueList should be list containing multiple dictonary. 92 | def insert(self,query,valuelist): 93 | result=db.execute(text(query),valuelist) 94 | self.del_engine() 95 | self.status=True 96 | self.rows_effected = result.rowcount 97 | return self -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os,uvicorn 2 | from fastapi import APIRouter, FastAPI 3 | from fastapi.responses import JSONResponse,PlainTextResponse 4 | from db.engine import db 5 | from fastapi.exceptions import RequestValidationError 6 | from fastapi.openapi.utils import get_openapi 7 | from fastapi.encoders import jsonable_encoder 8 | from fastapi.middleware.cors import CORSMiddleware 9 | import sentry_sdk 10 | from sentry_sdk.integrations.asgi import SentryAsgiMiddleware 11 | from middleware.middleware import * 12 | 13 | from routers.demo.demo import demoRoute 14 | 15 | from utils.invalid_response_class import InternalServerError 16 | from utils.response_manipulator import CustomResponse 17 | from utils.logging import init_logging 18 | 19 | if os.getenv('ENVIRONMENT')=="PRODUCTION" or os.getenv('ENVIRONMENT')=="STAGING": 20 | 21 | dsn = os.getenv('DSN') 22 | sentry_sdk.init( 23 | dsn= f"{dsn}", 24 | 25 | # Set traces_sample_rate to 1.0 to capture 100% 26 | # of transactions for performance monitoring. 27 | # We recommend adjusting this value in production. 28 | traces_sample_rate=1.0, 29 | environment=str(os.getenv("ENVIRONMENT")) 30 | ) 31 | 32 | app = FastAPI() 33 | app.add_middleware(SentryAsgiMiddleware) 34 | 35 | app.add_middleware(CORSMiddleware, allow_origins=['*'], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) 36 | app.include_router(demoRoute, prefix='/api/fv1/demo', tags=["Demo"],dependencies=[Depends(create_request_id)]) 37 | 38 | @app.exception_handler(InvalidationException) 39 | async def invalidation_exception_handler(request: Request, exc: InvalidationException): 40 | return CustomResponse(status_code=401,message=f"Invalid {exc.name}",request=request).customResp() 41 | 42 | @app.exception_handler(RequestValidationError) 43 | async def handle_error(request: Request, exc: RequestValidationError): 44 | data=jsonable_encoder(exc.errors()) 45 | return CustomResponse(status_code=400,message=f"Invalid Request",data=data,request=request).customResp() 46 | 47 | @app.exception_handler(InternalServerError) 48 | async def internal_server_error(request: Request, exc: InternalServerError): 49 | return CustomResponse(status_code=400,message="Some Error Occured",request=request).customResp() 50 | 51 | init_logging() 52 | 53 | @app.get('/ping', tags=['system']) 54 | async def ping(): 55 | return {'ok': 'ok'} 56 | 57 | if __name__ == "__main__": 58 | uvicorn.run("main:app", host="0.0.0.0", port=7000,reload=True,workers=4) -------------------------------------------------------------------------------- /middleware/middleware.py: -------------------------------------------------------------------------------- 1 | 2 | from ast import literal_eval 3 | from pprint import pprint 4 | from sqlalchemy import text 5 | import json 6 | import uuid 7 | from fastapi import Depends, FastAPI, Header,Request 8 | from db.engine import db 9 | 10 | from utils.invalid_response_class import InvalidationException 11 | from utils.logData import LogDataClass 12 | 13 | exclude_auth={ 14 | "users/sdk/auth":"API", 15 | } 16 | 17 | def authenticate_user(request: Request): 18 | input=[] 19 | 20 | headers_params=request.headers 21 | 22 | api_url=str(request.url).split('fv1/')[1] 23 | 24 | auth_verification=exclude_auth.get(api_url) 25 | if auth_verification=="API": 26 | api_key=headers_params.get('X-api-key') 27 | 28 | if api_key: 29 | # mysql=db.connection() 30 | result=db.execute(text("""select * from enterprise_account where api_key= :api_key """),api_key=api_key).fetchone() 31 | if result: 32 | request.state.enterprise_data=result._mapping 33 | else: 34 | raise InvalidationException("X-api-key") 35 | else: 36 | raise InvalidationException("X-api-key") 37 | elif auth_verification: 38 | request.state.enterprise_data=[] 39 | request.state.user_data=[] 40 | else: 41 | auth_key=headers_params.get('bearer-token') 42 | 43 | if auth_key: 44 | # mysql=db.connection() 45 | result=db.execute(text("""select user_id,enterprise_id,email_id,user_name,contact_no from enterprise_users_account where sso_token= :auth_key or secret_key= :secret_key """),auth_key=auth_key,secret_key=auth_key).fetchone() 46 | if result: 47 | request.state.user_data=result._mapping 48 | else: 49 | raise InvalidationException("Auth_key") 50 | else: 51 | raise InvalidationException("Auth_key") 52 | 53 | 54 | 55 | def create_request_id(request: Request): 56 | request_id=request._headers.get('X-Request-ID') 57 | 58 | 59 | if request_id: 60 | request.state.request_token=str(request_id) 61 | else: 62 | request_id=str(uuid.uuid4()) 63 | request.state.request_token=request_id 64 | LogDataClass(request_id=request_id).request_log(request) 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /routers/demo/demo.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Form,Request,File 2 | 3 | from utils.invalid_response_class import InternalServerError 4 | from utils.response_manipulator import CustomResponse 5 | from utils.exception import Exception 6 | 7 | 8 | 9 | demoRoute = APIRouter() 10 | 11 | @demoRoute.post('/demo') 12 | def demo(request: Request): 13 | try: 14 | # Dummy Response 15 | data={ 16 | "resp":"Success" 17 | } 18 | return CustomResponse(status_code=200, data=data, message="Success", request=request).customResp() 19 | 20 | except: 21 | raise Exception().raise_exception(request_id=request.state.request_token) 22 | 23 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | imageName=fast-api 2 | containerName=fast-api 3 | 4 | sudo docker build -t $imageName . -f ./Dockerfile 5 | 6 | echo Delete old container... 7 | sudo docker rm -f $containerName 8 | 9 | echo Run new container... 10 | sudo docker run -e ENVIRONMENT=STAGING -d -p 8000:7000 --name $containerName $imageName -------------------------------------------------------------------------------- /utils/exception.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | import json 4 | from sentry_sdk import * 5 | from utils.invalid_response_class import InternalServerError 6 | 7 | 8 | class Exception: 9 | def __init__(self): 10 | 11 | exception_type, exception_object, exception_traceback = sys.exc_info() 12 | filename = exception_traceback.tb_frame.f_code.co_filename 13 | line_number = exception_traceback.tb_lineno 14 | self.Error_data={ 15 | "exception_type":str(exception_type), 16 | "exception_object":str(exception_object), 17 | "exception_traceback":str(traceback.format_exc()), 18 | "line_number":str(line_number), 19 | "filename":str(filename), 20 | } 21 | with push_scope() as scope: 22 | scope.set_extra("custom_exp_obj",self.Error_data) 23 | capture_exception(exception_object) 24 | 25 | self.exception_obj={ 26 | "status":500, 27 | "message":str(exception_object), 28 | "traceback":str(traceback.format_exc()), 29 | "Error_Data":self.Error_data, 30 | } 31 | 32 | def raise_exception(self,request_id): 33 | raise InternalServerError(self.Error_data,request_id) 34 | 35 | def return_json(self): 36 | return json.dumps(self.exception_obj) 37 | 38 | -------------------------------------------------------------------------------- /utils/helpers.py: -------------------------------------------------------------------------------- 1 | import random,string 2 | import datetime 3 | 4 | def ran_string(size): 5 | return ''.join(random.choices(string.ascii_letters + string.digits, k = size)) 6 | 7 | 8 | def current_datetime(): 9 | # added date time function here so that we won't have import datetime everywhere is needed. Function can directly be imported from here. 10 | return datetime.datetime.now() -------------------------------------------------------------------------------- /utils/invalid_response_class.py: -------------------------------------------------------------------------------- 1 | from cgitb import reset 2 | import json 3 | from utils.logData import LogDataClass 4 | from loguru import logger 5 | 6 | class InvalidationException(Exception): 7 | def __init__(self, name: str): 8 | self.name = name 9 | 10 | 11 | class InternalServerError(Exception): 12 | def __init__(self, exception_dict : dict, request_id : str): 13 | LogDataClass(request_id).exception_log(exception_dict) 14 | -------------------------------------------------------------------------------- /utils/logData.py: -------------------------------------------------------------------------------- 1 | import json,os 2 | from pprint import pprint 3 | from loguru import logger 4 | from utils.helpers import current_datetime 5 | 6 | 7 | from .logging import init_logging 8 | 9 | init_logging() 10 | 11 | 12 | job_dict={} 13 | 14 | 15 | class LogDataClass(object): 16 | 17 | 18 | def __init__(self ,request_id ) -> None: 19 | 20 | self.request_id=request_id 21 | self.job_dict={ 22 | 23 | "@fields":{ 24 | 'level':"info" 25 | }, 26 | "@message" :{ 27 | "request_id":self.request_id, 28 | "time":str(current_datetime()), 29 | } 30 | 31 | } 32 | 33 | def log_data(self): 34 | if os.getenv('ENVIRONMENT')=="DEVELOPMENT": 35 | pprint(self.job_dict) 36 | logger.info(self.job_dict) 37 | 38 | 39 | 40 | def general_log(self,data): 41 | self.job_dict['data']=data 42 | self.log_data() 43 | # self.log.opt(depth=1,colors=True).info(self.job_dict) 44 | 45 | def request_log(self,request): 46 | self.job_dict["@message"].update(dict(request.headers)) 47 | self.job_dict["@message"]['body']=dict(request._form) 48 | self.job_dict["@message"]['params']=dict(request.query_params) 49 | self.job_dict["@message"]['url']=str(request.url) 50 | self.job_dict["@message"]['method']=str(request.method) 51 | self.log_data() 52 | 53 | 54 | 55 | def response_log(self,response): 56 | self.job_dict["@message"].update(dict(response.headers)) 57 | self.job_dict["@message"]['body']=json.loads(response.body) 58 | self.job_dict["@message"]['response_status_code']=response.status_code 59 | self.log_data() 60 | 61 | 62 | def exception_log(self,exception_dict={}): 63 | self.job_dict['@fields']['level']="Warn" 64 | self.job_dict['@message'].update(exception_dict) 65 | self.log_data() 66 | 67 | -------------------------------------------------------------------------------- /utils/logging.py: -------------------------------------------------------------------------------- 1 | """Configure handlers and formats for application loggers.""" 2 | from fileinput import filename 3 | import logging 4 | import sys 5 | from os import environ 6 | from pprint import pformat 7 | 8 | # if you dont like imports of private modules 9 | # you can move it to typing.py module 10 | from loguru import logger 11 | from loguru._defaults import LOGURU_FORMAT 12 | 13 | 14 | class InterceptHandler(logging.Handler): 15 | """ 16 | Default handler from examples in loguru documentaion. 17 | See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging 18 | """ 19 | 20 | def emit(self, record: logging.LogRecord): 21 | # Get corresponding Loguru level if it exists 22 | try: 23 | level = logger.level(record.levelname).name 24 | except ValueError: 25 | level = record.levelno 26 | 27 | # Find caller from where originated the logged message 28 | frame, depth = logging.currentframe(), 2 29 | while frame.f_code.co_filename == logging.__file__: 30 | frame = frame.f_back 31 | depth += 1 32 | 33 | logger.opt(depth=depth, exception=record.exc_info).log( 34 | level, record.getMessage() 35 | ) 36 | 37 | def env(key, type_, default=None): 38 | if key not in environ: 39 | return default 40 | 41 | val = environ[key] 42 | 43 | if type_ == str: 44 | return val 45 | elif type_ == bool: 46 | if val.lower() in ["1", "true", "yes", "y", "ok", "on"]: 47 | return True 48 | if val.lower() in ["0", "false", "no", "n", "nok", "off"]: 49 | return False 50 | raise ValueError( 51 | "Invalid environment variable '%s' (expected a boolean): '%s'" % (key, val) 52 | ) 53 | elif type_ == int: 54 | try: 55 | return int(val) 56 | except ValueError: 57 | raise ValueError( 58 | "Invalid environment variable '%s' (expected an integer): '%s'" % (key, val) 59 | ) from None 60 | 61 | def format_record(record: dict) -> str: 62 | """ 63 | Custom format for loguru loggers. 64 | Uses pformat for log any data like request/response body during debug. 65 | Works with logging if loguru handler it. 66 | Example: 67 | >>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True}, {"name": "Alex", "age": 27, "is_active": True}], "count": 2}] 68 | >>> logger.bind(payload=).debug("users payload") 69 | >>> [ { 'count': 2, 70 | >>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'}, 71 | >>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}] 72 | """ 73 | LOGURU_FORMAT = env( 74 | "LOGURU_FORMAT", 75 | str, 76 | "\n{message}", 77 | ) 78 | format_string = LOGURU_FORMAT 79 | if record["extra"].get("payload") is not None: 80 | record["extra"]["payload"] = pformat( 81 | record["extra"]["payload"], indent=4, compact=True, width=88 82 | ) 83 | format_string += "\n{extra[payload]}" 84 | 85 | # format_string += "{exception}" 86 | # format_string='{message}' 87 | return format_string 88 | 89 | 90 | def init_logging(): 91 | """ 92 | Replaces logging handlers with a handler for using the custom handler. 93 | 94 | WARNING! 95 | if you call the init_logging in startup event function, 96 | then the first logs before the application start will be in the old format 97 | >>> app.add_event_handler("startup", init_logging) 98 | stdout: 99 | INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) 100 | INFO: Started reloader process [11528] using statreload 101 | INFO: Started server process [6036] 102 | INFO: Waiting for application startup. 103 | 2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete. 104 | 105 | """ 106 | 107 | # disable handlers for specific uvicorn loggers 108 | # to redirect their output to the default uvicorn logger 109 | # works with uvicorn==0.11.6 110 | loggers = ( 111 | logging.getLogger(name) 112 | for name in logging.root.manager.loggerDict 113 | if name.startswith("uvicorn.") 114 | ) 115 | for uvicorn_logger in loggers: 116 | uvicorn_logger.handlers = [] 117 | 118 | # change handler for default uvicorn logger 119 | intercept_handler = InterceptHandler() 120 | logging.getLogger("uvicorn").handlers = [intercept_handler] 121 | 122 | # set logs output, level and format 123 | logger.configure( 124 | handlers=[{"sink": "log_file.log", "level": logging.DEBUG, "format": format_record}, 125 | {"sink": sys.stdout, "level": logging.DEBUG,"format": format_record}] 126 | ) -------------------------------------------------------------------------------- /utils/response_manipulator.py: -------------------------------------------------------------------------------- 1 | from email import message 2 | import json 3 | from fastapi import APIRouter, Form,Request ,Response 4 | from fastapi.responses import JSONResponse 5 | from fastapi.encoders import jsonable_encoder 6 | from utils.logData import LogDataClass 7 | 8 | 9 | class CustomResponse(object): 10 | # status_code: int 11 | # request : Request 12 | # responseData : {} 13 | 14 | def __init__(self ,**kwargs ) -> None: 15 | self.status_code=kwargs.get('status_code') 16 | self.responseData={ 17 | "message":kwargs.get("message") 18 | } 19 | if kwargs.get('data'): 20 | self.responseData['data']=kwargs.get('data') 21 | self.request=kwargs.get('request') 22 | 23 | def customResp(self): 24 | response_log={ 25 | "request_id":self.request.state.request_token, 26 | "data":self.responseData 27 | } 28 | 29 | response=JSONResponse(status_code=self.status_code, content=self.responseData ) 30 | response.set_cookie(key='request_id',value=self.request.state.request_token) 31 | LogDataClass(request_id=self.request.state.request_token).response_log(response) 32 | return response 33 | 34 | def basic_reponse(**kwargs): 35 | Resp=CustomResponse(kwargs) 36 | return Resp.customResp() 37 | 38 | 39 | --------------------------------------------------------------------------------