Skip to content

Commit c09c338

Browse files
committed
wip: add structlog
1 parent 3f09b57 commit c09c338

File tree

5 files changed

+73
-81
lines changed

5 files changed

+73
-81
lines changed

app/database.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
44

55
from app.config import settings as global_settings
6-
from app.utils.logging import AppLogger
7-
8-
logger = AppLogger().get_logger()
6+
from app.utils.logging import setup_structlog
7+
logger = setup_structlog()
98

109
engine = create_async_engine(
1110
global_settings.asyncpg_url.unicode_string(),

app/main.py

Lines changed: 46 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import logging
22
import os
33
from contextlib import asynccontextmanager
4-
from logging.handlers import RotatingFileHandler
54
from pathlib import Path
65

76
import asyncpg
87
import orjson
9-
10-
# from apscheduler import AsyncScheduler
11-
# from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
12-
# from apscheduler.eventbrokers.redis import RedisEventBroker
8+
import structlog
139
from fastapi import Depends, FastAPI, Request
1410
from fastapi.responses import HTMLResponse
1511
from fastapi.templating import Jinja2Templates
16-
from whenever._whenever import Instant
1712

1813
from app.api.health import router as health_router
1914
from app.api.ml import router as ml_router
@@ -22,93 +17,68 @@
2217
from app.api.stuff import router as stuff_router
2318
from app.api.user import router as user_router
2419
from app.config import settings as global_settings
25-
26-
# from app.database import engine
2720
from app.redis import get_redis
2821
from app.services.auth import AuthBearer
22+
from whenever._whenever import Instant
23+
from app.utils.logging import setup_structlog
2924

30-
# from app.services.scheduler import SchedulerMiddleware
31-
import structlog
32-
33-
log_date = Instant.now().py_datetime().strftime("%Y%m%d")
34-
35-
structlog.configure(
36-
cache_logger_on_first_use=True,
37-
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
38-
processors=[
39-
structlog.contextvars.merge_contextvars,
40-
structlog.processors.add_log_level,
41-
structlog.processors.format_exc_info,
42-
structlog.processors.TimeStamper(fmt="iso", utc=True),
43-
structlog.processors.JSONRenderer(serializer=orjson.dumps),
44-
],
45-
# log per day and per process?
46-
logger_factory=structlog.BytesLoggerFactory(
47-
file=Path(f"cuul_{log_date}_{str(os.getpid())}").with_suffix(".log").open("wb")
48-
)
49-
)
50-
51-
logger = structlog.get_logger()
5225

26+
logger = setup_structlog()
5327
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
5428

55-
5629
@asynccontextmanager
57-
async def lifespan(_app: FastAPI):
58-
# Load the redis connection
59-
_app.redis = await get_redis()
60-
61-
_postgres_dsn = global_settings.postgres_url.unicode_string()
62-
30+
async def lifespan(app: FastAPI):
31+
app.redis = await get_redis()
32+
postgres_dsn = global_settings.postgres_url.unicode_string()
6333
try:
64-
# TODO: cache with the redis connection
65-
# Initialize the postgres connection pool
66-
_app.postgres_pool = await asyncpg.create_pool(
67-
dsn=_postgres_dsn,
34+
app.postgres_pool = await asyncpg.create_pool(
35+
dsn=postgres_dsn,
6836
min_size=5,
6937
max_size=20,
7038
)
71-
logger.info("Postgres pool created", _app.postgres_pool.get_idle_size())
39+
logger.info("Postgres pool created", idle_size=app.postgres_pool.get_idle_size())
7240
yield
7341
finally:
74-
# close redis connection and release the resources
75-
await _app.redis.close()
76-
# close postgres connection pool and release the resources
77-
await _app.postgres_pool.close()
78-
79-
80-
app = FastAPI(title="Stuff And Nonsense API", version="0.19.0", lifespan=lifespan)
81-
82-
app.include_router(stuff_router)
83-
app.include_router(nonsense_router)
84-
app.include_router(shakespeare_router)
85-
app.include_router(user_router)
86-
app.include_router(ml_router, prefix="/v1/ml", tags=["ML"])
87-
88-
89-
app.include_router(health_router, prefix="/v1/public/health", tags=["Health, Public"])
90-
app.include_router(
91-
health_router,
92-
prefix="/v1/health",
93-
tags=["Health, Bearer"],
94-
dependencies=[Depends(AuthBearer())],
95-
)
42+
await app.redis.close()
43+
await app.postgres_pool.close()
44+
45+
def create_app() -> FastAPI:
46+
app = FastAPI(
47+
title="Stuff And Nonsense API",
48+
version="0.19.0",
49+
lifespan=lifespan,
50+
)
51+
app.include_router(stuff_router)
52+
app.include_router(nonsense_router)
53+
app.include_router(shakespeare_router)
54+
app.include_router(user_router)
55+
app.include_router(ml_router, prefix="/v1/ml", tags=["ML"])
56+
app.include_router(health_router, prefix="/v1/public/health", tags=["Health, Public"])
57+
app.include_router(
58+
health_router,
59+
prefix="/v1/health",
60+
tags=["Health, Bearer"],
61+
dependencies=[Depends(AuthBearer())],
62+
)
9663

64+
@app.get("/index", response_class=HTMLResponse)
65+
def get_index(request: Request):
66+
return templates.TemplateResponse("index.html", {"request": request})
9767

98-
@app.get("/index", response_class=HTMLResponse)
99-
def get_index(request: Request):
100-
return templates.TemplateResponse("index.html", {"request": request})
68+
return app
10169

70+
app = create_app()
10271

72+
# --- Unused/experimental code and TODOs ---
73+
# from apscheduler import AsyncScheduler
74+
# from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
75+
# from apscheduler.eventbrokers.redis import RedisEventBroker
76+
# from app.database import engine
77+
# from app.services.scheduler import SchedulerMiddleware
10378
# _scheduler_data_store = SQLAlchemyDataStore(engine, schema="scheduler")
104-
# _scheduler_event_broker = RedisEventBroker(
105-
# client_or_url=global_settings.redis_url.unicode_string()
106-
# )
79+
# _scheduler_event_broker = RedisEventBroker(client_or_url=global_settings.redis_url.unicode_string())
10780
# _scheduler_himself = AsyncScheduler(_scheduler_data_store, _scheduler_event_broker)
108-
#
10981
# app.add_middleware(SchedulerMiddleware, scheduler=_scheduler_himself)
110-
111-
112-
# TODO: every not GET meth should reset cache
113-
# TODO: every scheduler task which needs to act on database should have access to connection pool via request - maybe ?
114-
# TODO: https://stackoverflow.com/questions/16053364/make-sure-only-one-worker-launches-the-apscheduler-event-in-a-pyramid-web-app-ru
82+
# TODO: every non-GET method should reset cache
83+
# TODO: scheduler tasks needing DB should access connection pool via request
84+
# TODO: https://stackoverflow.com/questions/16053364/make-sure-only-one-worker-launches-the-apscheduler-event-in-a-pyramid-web-app-ru

app/utils/logging.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55

66
from app.utils.singleton import SingletonMeta
77

8+
import logging
9+
import os
10+
import orjson
11+
import structlog
12+
from whenever._whenever import Instant
13+
from pathlib import Path
814

915
class AppLogger(metaclass=SingletonMeta):
1016
_logger = None
@@ -22,3 +28,22 @@ def __init__(self, width=200, style=None, **kwargs):
2228
console=Console(color_system="256", width=width, style=style, stderr=True),
2329
**kwargs,
2430
)
31+
32+
def setup_structlog() -> structlog.BoundLogger:
33+
log_date = Instant.now().py_datetime().strftime("%Y%m%d")
34+
log_path = Path(f"cuul_{log_date}_{os.getpid()}.log")
35+
structlog.configure(
36+
cache_logger_on_first_use=True,
37+
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
38+
processors=[
39+
structlog.contextvars.merge_contextvars,
40+
structlog.processors.add_log_level,
41+
structlog.processors.format_exc_info,
42+
structlog.processors.TimeStamper(fmt="iso", utc=True),
43+
structlog.processors.JSONRenderer(serializer=orjson.dumps),
44+
],
45+
logger_factory=structlog.BytesLoggerFactory(
46+
file=log_path.open("wb")
47+
)
48+
)
49+
return structlog.get_logger()

compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ services:
1010
- .secrets
1111
command: bash -c "
1212
uvicorn app.main:app
13-
--log-config ./logging-uvicorn.json
1413
--host 0.0.0.0 --port 8080
1514
--lifespan=on --use-colors --loop uvloop --http httptools
1615
--reload --log-level debug

granian-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ services:
1212
granian --interface asgi
1313
--host 0.0.0.0 --port 8080
1414
app.main:app --access-log --log-level debug
15-
--log-config ./logging-granian.json
1615
"
1716
volumes:
1817
- ./app:/panettone/app

0 commit comments

Comments
 (0)