Skip to content

Commit

Permalink
Add logger
Browse files Browse the repository at this point in the history
  • Loading branch information
rednafi committed Oct 25, 2024
1 parent 6dc3be2 commit 4543f21
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 138 deletions.
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ kill-container: ## Stop the running docker container.

.PHONY: run-local
run-local: ## Run the app locally.
uvicorn svc.main:app --port 5002 --reload
uv run uvicorn svc.main:app --port 5002 --reload
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ max-complexity = 10
#################################################

[tool.pytest.ini_options]
addopts = "--strict-markers --maxfail 1 --cov src tests/ --no-header"
addopts = "--strict-markers --maxfail 1 --cov svc tests/ --no-header"
markers = """
integration: mark a test as an integration test.
"""
Expand Down
8 changes: 2 additions & 6 deletions svc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import logging
import sys
from svc.core.logger import configure_logger

# Set default logging value to debug
logging.basicConfig(level=logging.INFO)

logging.info("Running fastapi-nano with Python %s", sys.version)
configure_logger()
30 changes: 30 additions & 0 deletions svc/core/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import logging

def configure_logger() -> None:
"""Configure a custom logger."""

# Create a logger
logger = logging.getLogger("fnano")
logger.setLevel(logging.INFO)

# Create a handler (console output in this case)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# Create a formatter and set it to the handler
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
console_handler.setFormatter(formatter)

# Add the handler to the logger
if not logger.hasHandlers():
logger.addHandler(console_handler)

# Disable propagation to avoid log duplication via uvicorn
logger.propagate = False

# Disable passlib logger
# See: <https://github.com/pyca/bcrypt/issues/684>
logging.getLogger("passlib").setLevel(logging.ERROR)
34 changes: 21 additions & 13 deletions svc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@
from svc.core import auth
from svc.routes import views

app = FastAPI()

# Set all CORS enabled origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(auth.router)
app.include_router(views.router)

def create_app() -> FastAPI:
"""Create a FastAPI application."""

app = FastAPI()

# Set all CORS enabled origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(auth.router)
app.include_router(views.router)
return app


app = create_app()
11 changes: 8 additions & 3 deletions svc/routes/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from svc.apis.api_a.mainmod import main_func as main_func_a
from svc.apis.api_b.mainmod import main_func as main_func_b
from svc.core.auth import get_current_user
import logging

router = APIRouter()

logger = logging.getLogger(__name__)

@router.get("/")
async def index() -> dict[str, str]:
Expand All @@ -22,12 +23,16 @@ async def view_a(
num: int,
auth: Depends = Depends(get_current_user),
) -> dict[str, int]:
return main_func_a(num)
result = main_func_a(num)
logger.info(f"API A: {result}")
return result


@router.get("/api_b/{num}", tags=["api_b"])
async def view_b(
num: int,
auth: Depends = Depends(get_current_user),
) -> dict[str, int]:
return main_func_b(num)
result = main_func_b(num)
logger.info(f"API B: {result}")
return result
80 changes: 80 additions & 0 deletions svc/tests/test_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import logging

"""Test suite for the logger module."""

import logging
from collections.abc import Iterator
from io import StringIO

import pytest
from _pytest.logging import LogCaptureFixture # for caplog fixture typing

from svc.core.logger import configure_logger


@pytest.fixture
def canned_logger() -> Iterator[logging.Logger]:
"""Fixture to configure the logger for tests with propagate set to True."""
logger = logging.getLogger("fnano")

# Clear any handlers attached to the logger from previous tests
logger.handlers = []

# Call the function that configures the logger
configure_logger()

# Set logger propagation to True for tests
original_propagate = logger.propagate
logger.propagate = True

# Yield the logger for use in tests
yield logger

# Reset the logger propagate to the original value after test
logger.propagate = original_propagate


def test_configure_logger(
canned_logger: logging.Logger, caplog: LogCaptureFixture
) -> None:
# Test if logger is configured properly
with caplog.at_level(logging.INFO, logger="fnano"):
canned_logger.info("Test log message")

# Check if the log was captured and formatted correctly
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "INFO"
assert caplog.records[0].message == "Test log message"

# Check if the time format in the log is correct
log_time = caplog.records[0].asctime
assert isinstance(log_time, str)
assert len(log_time) == 19 # "YYYY-MM-DD HH:MM:SS" is 19 characters long


def test_log_output_format(canned_logger: logging.Logger) -> None:
# Set up a StringIO stream to capture log output
stream = StringIO()
handler = logging.StreamHandler(stream)
handler.setLevel(logging.INFO)

formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
handler.setFormatter(formatter)

canned_logger.handlers = [] # Remove any pre-existing handlers
canned_logger.addHandler(handler)
canned_logger.setLevel(logging.INFO)

# Log a message and capture the output
canned_logger.info("Test log message")

# Flush the handler and get the output
handler.flush()
log_output = stream.getvalue()

# Check the log output format
assert "fnano - INFO - Test log message" in log_output
assert log_output.startswith("20") # The log should start with a year like "2024"
Loading

0 comments on commit 4543f21

Please sign in to comment.