Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bluemath_tk/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
EARTH_RADIUS = 6378.135 # Earth radius in km
EARTH_RADIUS_NM = EARTH_RADIUS / 1.852 # Earth radius in nautical miles
GRAVITY = 9.81 # Gravitational acceleration in m/s2
87 changes: 63 additions & 24 deletions bluemath_tk/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@
import pytz


def _coerce_level(level: Union[int, str]) -> int:
"""Return a numeric logging level from an int or level name."""
if isinstance(level, int):
return level
name = str(level).upper()
value = logging.getLevelName(name)
if isinstance(value, int) and value != 0:
return value
raise ValueError(f"Unknown log level: {level!r}")


def _console_handlers(logger: logging.Logger) -> list[logging.Handler]:
return [
handler
for handler in logger.handlers
if isinstance(handler, logging.StreamHandler)
and not isinstance(handler, logging.FileHandler)
]


def _file_handlers(logger: logging.Logger) -> list[logging.FileHandler]:
return [
handler
for handler in logger.handlers
if isinstance(handler, logging.FileHandler)
]


def get_file_logger(
name: str,
logs_path: str = None,
Expand All @@ -23,16 +51,23 @@ def get_file_logger(
logs_path : str, optional
The file path where the log messages will be written. Default is None.
level : Union[int, str], optional
The logging level. Default is "INFO".
The logging level for the logger and file handler. Default is "INFO".
console : bool
Whether to add or not console / terminal logs. Default is True.
console_level : Union[int, str], optional
The logging level for console / terminal logs. Default is "WARNING".

Returns
-------
logging.Logger
Configured logger instance.

Notes
-----
Safe to call more than once for the same *name*: existing handlers are
updated (file level, console level, console on/off) instead of returning
a stale configuration.

Examples
--------
>>> from bluemath_tk.core.logging import get_file_logger
Expand All @@ -48,39 +83,43 @@ def get_file_logger(
>>> # 2023-10-22 14:55:23,458 - my_app_logger - ERROR - This is an error message.
"""

# If a logger with the specified name already exists, return it
if name in logging.Logger.manager.loggerDict:
return logging.getLogger(name)
file_level = _coerce_level(level)
stream_level = _coerce_level(console_level)

# Create a logger with the specified name
logger = logging.getLogger(name)
logger.setLevel(level)
logger.propagate = False # Avoid duplicate logs

# Get current date to append to logs_path
date_str = datetime.now(pytz.timezone("Europe/Madrid")).strftime("%Y-%m-%d")

# Create a file handler to write logs to the specified file
if logs_path is None:
os.makedirs("logs", exist_ok=True)
logs_path = os.path.join("logs", f"{name.strip()}_{date_str}.log")
else:
os.makedirs(os.path.dirname(logs_path))
file_handler = logging.FileHandler(logs_path)
logger.setLevel(file_level)
logger.propagate = False # Avoid duplicate logs via the root logger

# Define a logging format
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(formatter)

# Add the file handler to the logger
logger.addHandler(file_handler)
file_handlers = _file_handlers(logger)
if file_handlers:
for handler in file_handlers:
handler.setLevel(file_level)
if handler.formatter is None:
handler.setFormatter(formatter)
else:
date_str = datetime.now(pytz.timezone("Europe/Madrid")).strftime("%Y-%m-%d")
if logs_path is None:
os.makedirs("logs", exist_ok=True)
logs_path = os.path.join("logs", f"{name.strip()}_{date_str}.log")
else:
log_dir = os.path.dirname(logs_path)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(logs_path)
file_handler.setLevel(file_level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

for handler in _console_handlers(logger):
logger.removeHandler(handler)

# Also ouput logs in the console if requested
if console:
console_handler = logging.StreamHandler()
console_handler.setLevel(console_level)
console_handler.setLevel(stream_level)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

Expand Down
Loading
Loading