Skip to content

Commit cc6e85a

Browse files
committed
Allow to configurate logging directly form environment variables
1 parent fc3a800 commit cc6e85a

File tree

2 files changed

+40
-37
lines changed

2 files changed

+40
-37
lines changed

src/app/core/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ class AppSettings(BaseSettings):
1414
CONTACT_EMAIL: str | None = None
1515

1616

17+
class LogLevelOption(str, Enum):
18+
DEBUG = "DEBUG"
19+
INFO = "INFO"
20+
WARNING = "WARNING"
21+
ERROR = "ERROR"
22+
23+
24+
class LoggingSettings(BaseSettings):
25+
LOG_LEVEL: LogLevelOption = LogLevelOption.INFO
26+
LOG_FORMAT_AS_JSON: bool = False
27+
LOG_TO_FILE: bool = True
28+
29+
1730
class CryptSettings(BaseSettings):
1831
SECRET_KEY: SecretStr = SecretStr("secret-key")
1932
ALGORITHM: str = "HS256"
@@ -164,6 +177,7 @@ class Settings(
164177
CRUDAdminSettings,
165178
EnvironmentSettings,
166179
CORSSettings,
180+
LoggingSettings,
167181
):
168182
model_config = SettingsConfigDict(
169183
env_file=os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", ".env"),

src/app/core/logger.py

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import logging
22
import logging.config
3-
import os
43
from datetime import UTC, datetime
54
from pathlib import Path
65
from typing import Any
76

87
from pythonjsonlogger.json import JsonFormatter
98

10-
from .config import EnvironmentOption, settings
9+
from .config import settings
1110

1211

1312
class ColoredFormatter(logging.Formatter):
@@ -30,17 +29,6 @@ def format(self, record: logging.LogRecord) -> str:
3029
return super().format(record_copy)
3130

3231

33-
def get_log_level() -> int:
34-
"""Get log level from environment with validation."""
35-
log_level_name = os.getenv("LOG_LEVEL", "INFO").upper()
36-
37-
level = logging.getLevelNamesMapping().get(log_level_name)
38-
if level is None:
39-
raise ValueError(f"Invalid log level '{log_level_name}'")
40-
41-
return level
42-
43-
4432
def log_directory() -> Path:
4533
"""Ensure log directory exists and return the path."""
4634
log_dir = Path(__file__).parent.parent.parent / "logs"
@@ -50,19 +38,18 @@ def log_directory() -> Path:
5038

5139
def get_logging_config() -> dict[str, Any]:
5240
"""Get logging configuration based on environment."""
53-
log_level = get_log_level()
54-
41+
log_level = settings.LOG_LEVEL.value
5542
# Base configuration
5643
config: dict[str, Any] = {
5744
"version": 1,
5845
"disable_existing_loggers": False,
5946
"formatters": {
60-
"development": {
47+
"colored_text": {
6148
"()": ColoredFormatter,
6249
"format": "%(asctime)s- %(levelname)s - %(name)s - %(message)s",
6350
"datefmt": "%Y-%m-%d %H:%M:%S",
6451
},
65-
"file": {
52+
"plain_text": {
6653
"format": "%(asctime)s- %(levelname)s - %(name)s - %(message)s",
6754
"datefmt": "%Y-%m-%d %H:%M:%S",
6855
},
@@ -72,13 +59,17 @@ def get_logging_config() -> dict[str, Any]:
7259
},
7360
},
7461
"handlers": {
75-
"console": {"class": "logging.StreamHandler", "level": log_level, "stream": "ext://sys.stdout"},
62+
"console": {
63+
"class": "logging.StreamHandler",
64+
"level": log_level,
65+
"stream": "ext://sys.stdout",
66+
},
7667
},
77-
"root": {"level": log_level, "handlers": []},
68+
"root": {"level": log_level, "handlers": ["console"]},
7869
"loggers": {
7970
"uvicorn.access": {
8071
"level": "INFO",
81-
"handlers": [],
72+
"handlers": ["console"],
8273
"propagate": False, # Don't propagate to root logger to avoid double logging
8374
},
8475
"uvicorn.error": {"level": "INFO"},
@@ -89,8 +80,7 @@ def get_logging_config() -> dict[str, Any]:
8980
},
9081
}
9182

92-
# Environment-specific configuration
93-
if settings.ENVIRONMENT == EnvironmentOption.LOCAL:
83+
if settings.LOG_TO_FILE:
9484
# Create file handler only when needed
9585
log_dir = log_directory()
9686
# Keeping filename timestamp granularity to minutes to avoid too
@@ -108,16 +98,17 @@ def get_logging_config() -> dict[str, Any]:
10898
"backupCount": 5,
10999
"formatter": "file",
110100
}
101+
config["root"]["handlers"].append("file")
102+
config["loggers"]["uvicorn.access"]["handlers"].append("file")
111103

112-
# Plain colored text + file logging
113-
config["handlers"]["console"]["formatter"] = "development"
114-
config["root"]["handlers"] = ["console", "file"]
115-
config["loggers"]["uvicorn.access"]["handlers"] = ["console", "file"]
116-
else:
117-
# As JSON messages to console
104+
if settings.LOG_FORMAT_AS_JSON:
105+
# As JSON messages
118106
config["handlers"]["console"]["formatter"] = "json"
119-
config["root"]["handlers"] = ["console"]
120-
config["loggers"]["uvicorn.access"]["handlers"] = ["console"]
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"
121112

122113
return config
123114

@@ -129,12 +120,10 @@ def setup_logging() -> None:
129120

130121
# Log startup information
131122
logger = logging.getLogger(__name__)
132-
logger.info(f"Logging configured for {settings.ENVIRONMENT.value} environment")
133-
logger.info(f"Log level set to {logging.getLevelName(get_log_level())}")
123+
logger.info(f"Log level set to {settings.LOG_LEVEL.value}")
124+
if config["handlers"]["console"]["formatter"] == "json":
125+
logger.info("Logs will be written in JSON format")
126+
if "console" in config["root"]["handlers"]:
127+
logger.info("Logs will be written to the console")
134128
if "file" in config["root"]["handlers"]:
135129
logger.info(f"Logs will be written to the file {config['handlers']['file']['filename']}")
136-
if "console" in config["root"]["handlers"]:
137-
extra = ""
138-
if config["handlers"]["console"]["formatter"] == "json":
139-
extra = " in JSON format"
140-
logger.info(f"Logs will be written to the console{extra}")

0 commit comments

Comments
 (0)