Skip to content

Commit 726f585

Browse files
committed
Modify based on .env vars and simpligy logic
1 parent cc6e85a commit 726f585

File tree

5 files changed

+49
-16
lines changed

5 files changed

+49
-16
lines changed

scripts/local_with_uvicorn/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ CONTACT_NAME="Me"
2020
CONTACT_EMAIL="[email protected]"
2121
LICENSE_NAME="MIT"
2222

23+
# ------------- logging settings -------------
24+
LOG_LEVEL="INFO"
25+
LOG_FORMAT_AS_JSON=false
26+
LOG_TO_FILE=false
27+
2328
# ------------- database -------------
2429
POSTGRES_USER="postgres"
2530
POSTGRES_PASSWORD=1234

scripts/local_with_uvicorn/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ services:
1414
volumes:
1515
- ./src/app:/code/app
1616
- ./src/.env:/code/.env
17+
- ./logs:/code/logs
1718

1819
worker:
1920
build:

src/app/core/config.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import logging
12
import os
23
from enum import Enum
4+
from typing import Self
35

4-
from pydantic import SecretStr, computed_field
6+
from pydantic import SecretStr, computed_field, field_validator, model_validator
57
from pydantic_settings import BaseSettings, SettingsConfigDict
68

9+
logger = logging.getLogger(__name__)
10+
711

812
class AppSettings(BaseSettings):
913
APP_NAME: str = "FastAPI app"
@@ -24,7 +28,7 @@ class LogLevelOption(str, Enum):
2428
class LoggingSettings(BaseSettings):
2529
LOG_LEVEL: LogLevelOption = LogLevelOption.INFO
2630
LOG_FORMAT_AS_JSON: bool = False
27-
LOG_TO_FILE: bool = True
31+
LOG_TO_FILE: bool = False
2832

2933

3034
class CryptSettings(BaseSettings):
@@ -147,14 +151,19 @@ class CRUDAdminSettings(BaseSettings):
147151

148152

149153
class EnvironmentOption(str, Enum):
150-
LOCAL = "local"
151-
STAGING = "staging"
152-
PRODUCTION = "production"
154+
LOCAL = "LOCAL"
155+
STAGING = "STAGING"
156+
PRODUCTION = "PRODUCTION"
153157

154158

155159
class EnvironmentSettings(BaseSettings):
156160
ENVIRONMENT: EnvironmentOption = EnvironmentOption.LOCAL
157161

162+
@field_validator("ENVIRONMENT", mode="before")
163+
@classmethod
164+
def normalize_environment(cls, v: str) -> str:
165+
return v.upper()
166+
158167

159168
class CORSSettings(BaseSettings):
160169
CORS_ORIGINS: list[str] = ["*"]
@@ -186,5 +195,26 @@ class Settings(
186195
extra="ignore",
187196
)
188197

198+
@model_validator(mode="after")
199+
def validate_environment_settings(self) -> Self:
200+
"""The validation should not modify any of the settings.
201+
202+
It should provide feedback to the user if any misconfiguration is detected.
203+
"""
204+
environment = self.ENVIRONMENT.value
205+
if environment == EnvironmentOption.PRODUCTION:
206+
if self.LOG_LEVEL == LogLevelOption.DEBUG:
207+
logger.warning(
208+
f"In a {environment} environment, it's recommended to set LOG_LEVEL to INFO, WARNING, or ERROR. "
209+
"It is currently being set to DEBUG."
210+
)
211+
if self.LOG_FORMAT_AS_JSON is False:
212+
logger.warning(
213+
f"In a {environment} environment, it's recommended to set LOG_FORMAT_AS_JSON to true "
214+
"if you are using log aggregation tools."
215+
)
216+
217+
return self
218+
189219

190220
settings = Settings()

src/app/core/logger.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,9 @@ def log_directory() -> Path:
3737

3838

3939
def get_logging_config() -> dict[str, Any]:
40-
"""Get logging configuration based on environment."""
40+
"""Get logging configuration."""
4141
log_level = settings.LOG_LEVEL.value
42-
# Base configuration
43-
config: dict[str, Any] = {
42+
config = {
4443
"version": 1,
4544
"disable_existing_loggers": False,
4645
"formatters": {
@@ -63,6 +62,7 @@ def get_logging_config() -> dict[str, Any]:
6362
"class": "logging.StreamHandler",
6463
"level": log_level,
6564
"stream": "ext://sys.stdout",
65+
"formatter": "colored_text",
6666
},
6767
},
6868
"root": {"level": log_level, "handlers": ["console"]},
@@ -100,15 +100,13 @@ def get_logging_config() -> dict[str, Any]:
100100
}
101101
config["root"]["handlers"].append("file")
102102
config["loggers"]["uvicorn.access"]["handlers"].append("file")
103+
if settings.LOG_FORMAT_AS_JSON:
104+
config["handlers"]["file"]["formatter"] = "json"
105+
else:
106+
config["handlers"]["file"]["formatter"] = "plain_text"
103107

104108
if settings.LOG_FORMAT_AS_JSON:
105-
# As JSON messages
106109
config["handlers"]["console"]["formatter"] = "json"
107-
config["handlers"]["file"]["formatter"] = "json"
108-
else:
109-
# Colored text depending on the logging level
110-
config["handlers"]["console"]["formatter"] = "colored_text"
111-
config["handlers"]["file"]["formatter"] = "plain_text"
112110

113111
return config
114112

@@ -120,7 +118,7 @@ def setup_logging() -> None:
120118

121119
# Log startup information
122120
logger = logging.getLogger(__name__)
123-
logger.info(f"Log level set to {settings.LOG_LEVEL.value}")
121+
logger.info(f"Log level set to {config['root']['level']}")
124122
if config["handlers"]["console"]["formatter"] == "json":
125123
logger.info("Logs will be written in JSON format")
126124
if "console" in config["root"]["handlers"]:

src/app/core/setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ def create_application(
189189
for caching, queue, and rate limiting, client-side caching, and customizing the API documentation
190190
based on the environment settings.
191191
"""
192-
# Setup logging first based on the environment, before any other operations
193192
setup_logging()
194193

195194
if isinstance(settings, AppSettings):

0 commit comments

Comments
 (0)