Add sentry, datadog and structured logging

This commit is contained in:
Timothy Farrell 2025-12-28 18:51:40 -06:00
parent ef769e40b6
commit 6c07d47b1b
7 changed files with 109 additions and 8 deletions

View File

@ -4,7 +4,7 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings): class Settings(BaseSettings):
"""Application settings following 12-factor app principles.""" env: str = Field(default='DEV', alias="ENV")
# Server settings # Server settings
host: str = Field(default="0.0.0.0", alias="HOST") host: str = Field(default="0.0.0.0", alias="HOST")

View File

View File

@ -5,7 +5,7 @@ from sqlalchemy import MetaData, create_engine
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
from app.core.config import settings from app.config import settings
class Base(DeclarativeBase): class Base(DeclarativeBase):

42
app/logging.py Normal file
View File

@ -0,0 +1,42 @@
"""Logging configuration with structured logging."""
import logging
import sys
import structlog
from app.config import settings
def configure_logging() -> None:
"""Configure structured logging."""
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer(
) if not settings.debug else structlog.dev.ConsoleRenderer(),
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
# Configure standard logging
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=getattr(logging, settings.log_level.upper()),
)
def get_logger(name: str) -> structlog.BoundLogger:
"""Get a configured logger instance."""
return structlog.get_logger(name) # type:ignore[no-any-return]

36
app/middleware.py Normal file
View File

@ -0,0 +1,36 @@
from typing import Callable
from fastapi import Request, Response
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address
from app.config import settings
from app.logging import get_logger
logger = get_logger(__name__)
# Rate limiter
limiter = Limiter(key_func=get_remote_address)
async def logging_middleware(request: Request, call_next: Callable) -> Response:
"""Log all requests and responses."""
logger.debug(
"Request received",
method=request.method,
url=str(request.url),
client_ip=get_remote_address(request)
)
response = await call_next(request)
logger.debug(
"Response sent",
status_code=response.status_code,
method=request.method,
url=str(request.url)
)
return response

View File

@ -1,7 +1,7 @@
"""Health check endpoints.""" """Health check endpoints."""
from fastapi import APIRouter from fastapi import APIRouter
from app.core.database import DatabaseService from app.database import DatabaseService
router = APIRouter() router = APIRouter()

31
main.py
View File

@ -1,17 +1,38 @@
""" """
Main entry point for the Loan Operations API. Main entry point for the Loan Operations API.
""" """
import uvicorn import sentry_sdk
from fastapi import FastAPI from ddtrace import patch_all
from fastapi.middleware.cors import CORSMiddleware from fastapi import APIRouter, FastAPI
from sentry_sdk.integrations.fastapi import FastApiIntegration
from app.core.config import settings from app.config import settings
from app.logging import configure_logging, get_logger
from app.middleware import logging_middleware
from app.resources import health from app.resources import health
def create_app() -> FastAPI: def create_app() -> FastAPI:
"""Create and configure the FastAPI application.""" """Create and configure the FastAPI application."""
# Configure monitoring
if settings.sentry_dsn:
sentry_sdk.init(
dsn=settings.sentry_dsn,
integrations=[FastApiIntegration()],
traces_sample_rate=1.0,
environment=settings.dd_env,
)
if settings.dd_service:
# Configure Datadog tracing
patch_all()
# Configure logging
configure_logging()
logger = get_logger(__name__)
# Create FastAPI app
app = FastAPI( app = FastAPI(
title="Loan Operations API", title="Loan Operations API",
description="SBA Loan Operations API", description="SBA Loan Operations API",
@ -29,6 +50,8 @@ def create_app() -> FastAPI:
def main() -> None: def main() -> None:
"""Run the application.""" """Run the application."""
import uvicorn
uvicorn.run( uvicorn.run(
app, app,
host=settings.host, host=settings.host,