Merge pull request #87 from grillazz/75-revisit-logging

refactor base model crud meths
This commit is contained in:
Jakub Miazek 2023-04-28 13:59:27 +02:00 committed by GitHub
commit ba11bf78f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 51 deletions

View File

@ -50,6 +50,27 @@ Data set is coming form https://github.com/catherinedevlin/opensourceshakespeare
Next models were generated with https://github.com/agronholm/sqlacodegen
And after some tweaking I got desired result
### Rainbow logs with rich :rainbow:
To deliver better user(developer) experience when watching logs with tons of information
from few emitters (which are really needy on development stage) project is using [rich](https://github.com/Textualize/rich) library.
Event with [rich](https://github.com/Textualize/rich) superpowers reading logs is not easy.
Found [rich](https://github.com/Textualize/rich) really nice -
but it took time to learn how to integrate it as logger object properly and keep it as singleton.
To address below needs:
- it is hard to find what I am looking for even with glasses on.
- dont want to hire ELK to be able to use logs.
- want to move fast enough with debugging.
Below steps were done to integrate [rich](https://github.com/Textualize/rich) into project.
1. Configure emitters with [config.ini](https://github.com/grillazz/fastapi-sqlalchemy-asyncpg/blob/main/config.ini)
2. Eliminate duplicates i.e. sqlalchemy echo by separate handlers
3. Keep logger as singleton pattern to avoid multiple instances
4. add uvicorn parameter --log-config config.ini
![sample-logs-with-rich](/static/logz.png)
Hope you enjoy it.
### Change Log
@ -59,6 +80,7 @@ Hope you enjoy it.
- 12 NOV 2022 ruff implemented to project as linting tool
- 14 FEB 2023 bump project to Python 3.11
- 10 APR 2023 implement logging with rich
- 28 APR 2023 Rainbow logs with rich :rainbow:
### Local development with poetry

View File

@ -4,7 +4,7 @@ from asyncpg import UniqueViolationError
from fastapi import HTTPException, status
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy.orm import as_declarative, declared_attr
@as_declarative()
@ -53,16 +53,19 @@ class Base:
except SQLAlchemyError as ex:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
async def update(self, db_session: AsyncSession, **kwargs):
async def update(self, db: AsyncSession, **kwargs):
"""
:param db_session:
:param kwargs:
:param db:
:param kwargs
:return:
"""
try:
for k, v in kwargs.items():
setattr(self, k, v)
await self.save(db_session)
return await db.commit()
except SQLAlchemyError as ex:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
async def save_or_update(self, db: AsyncSession):
try:

View File

@ -1,55 +1,48 @@
[loggers]
keys = root, sqlalchemy
keys = root, sqlalchemy.engine.Engine, uvicorn.access
[handlers]
keys = console, console_rich, error_file, access_file
keys = stream, sqlalchemy, uvicorn
[formatters]
keys = generic, generic_rich, access
keys = default
[logger_root]
; Logging level for all loggers
level = NOTSET
handlers = console_rich, error_file
level = INFO
propagate = 0
handlers = stream
[logger_sqlalchemy]
handlers =
qualname = sqlalchemy.engine
[logger_sqlalchemy.engine.Engine]
level = INFO
propagate = 0
handlers = sqlalchemy
qualname = sqlalchemy.engine.Engine
[handler_console]
class = logging.StreamHandler
level = NOTSET
formatter = generic
stram = ext://sys.stdout
[logger_uvicorn.access]
level = INFO
propagate = 0
handlers = uvicorn
qualname = uvicorn.access
[handler_error_file]
class = logging.FileHandler
formatter = generic
level = WARNING
args = ('/tmp/error.log','w')
[handler_access_file]
class = logging.FileHandler
formatter = access
args = ('/tmp/access.log',)
[formatter_generic]
format = [%(process)d|%(name)-12s|%(filename)s:%(lineno)d] %(levelname)-7s %(message)s
datefmt = %H:%M:%S
class = logging.Formatter
[formatter_access]
format = %(message)s
class = logging.Formatter
[formatter_generic_rich]
format = [%(process)d %(name)s] %(message)s
datefmt = %H:%M:%S
class = logging.Formatter
[handler_console_rich]
[handler_stream]
class = app.logging.RichConsoleHandler
args = (100, "blue")
kwargs = {"omit_repeated_times":False, "show_time": False, "enable_link_path": True, "tracebacks_show_locals": True}
level = NOTSET
kwargs = {"omit_repeated_times":True, "show_time": False, "enable_link_path": False, "tracebacks_show_locals": True}
args = (100, "white")
formatter = default
stream = ext://sys.stdout
[handler_sqlalchemy]
class = app.logging.RichConsoleHandler
kwargs = {"omit_repeated_times":True, "show_time": False, "enable_link_path": False, "tracebacks_show_locals": True}
args = (100, "magenta")
formatter = default
[handler_uvicorn]
class = app.logging.RichConsoleHandler
kwargs = {"omit_repeated_times":True, "show_time": False, "enable_link_path": False, "tracebacks_show_locals": True}
args = (100, "yellow")
formatter = default
[formatter_default]
format = [%(process)d|%(name)-12s] %(message)s
class = logging.Formatter

BIN
static/logz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB