Skip to content

Commit b46fc43

Browse files
committed
🐛 Ensure log dir exists when initializing logger
1 parent 7495dc5 commit b46fc43

File tree

1 file changed

+62
-32
lines changed

1 file changed

+62
-32
lines changed

CPAC/utils/monitoring/custom_logging.py

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818

1919
import logging
2020
import os
21+
from pathlib import Path
2122
import subprocess
2223
from sys import exc_info as sys_exc_info
2324
from traceback import print_exception
24-
from typing import Optional, Sequence, TYPE_CHECKING
25+
from typing import Literal, Optional, Sequence, TYPE_CHECKING, TypeAlias
2526

2627
from nipype import config as nipype_config, logging as nipype_logging
2728

@@ -30,6 +31,31 @@
3031

3132
if TYPE_CHECKING:
3233
from CPAC.utils.configuration import Configuration
34+
LogLevel: TypeAlias = (
35+
Literal[
36+
"CRITICAL",
37+
"critical",
38+
"Critical",
39+
"DEBUG",
40+
"debug",
41+
"Debug",
42+
"ERROR",
43+
"error",
44+
"Error",
45+
"INFO",
46+
"info",
47+
"Info",
48+
"NOTSET",
49+
"notset",
50+
"Notset",
51+
"NotSet",
52+
"notSet",
53+
"WARNING",
54+
"warning",
55+
"Warning",
56+
]
57+
| int
58+
)
3359

3460

3561
def failed_to_start(log_dir, exception):
@@ -49,17 +75,8 @@ def failed_to_start(log_dir, exception):
4975
logger.exception(exception)
5076

5177

52-
def getLogger(name): # pylint: disable=invalid-name
53-
"""Get a mock logger if one exists, falling back on real loggers.
54-
55-
Parameters
56-
----------
57-
name : str
58-
59-
Returns
60-
-------
61-
logger : CPAC.utils.monitoring.custom_logging.MockLogger or logging.Logger
62-
"""
78+
def getLogger(name: str) -> "logging.Logger | MockLogger": # pylint: disable=invalid-name
79+
"""Get a mock logger if one exists, falling back on real loggers."""
6380
if name in MOCK_LOGGERS:
6481
return MOCK_LOGGERS[name]
6582
logger = nipype_logging.getLogger(name)
@@ -158,7 +175,10 @@ def __init__(self, filename):
158175
class MockLogger:
159176
"""Mock logging.Logger to provide API without keeping the logger in memory."""
160177

161-
def __init__(self, name, filename, level, log_dir):
178+
def __init__(
179+
self, name: str, filename: str, level: LogLevel, log_dir: Path | str
180+
) -> None:
181+
"""Initialize a mock logger."""
162182
self.name = name
163183
self.level = level
164184
self.handlers = [MockHandler(os.path.join(log_dir, filename))]
@@ -243,34 +263,38 @@ def _lazy_sub(message, *items):
243263

244264

245265
def set_up_logger(
246-
name, filename=None, level=None, log_dir=None, mock=False, overwrite_existing=False
247-
):
266+
name: str,
267+
filename: Optional[str] = None,
268+
level: Optional[LogLevel] = None,
269+
log_dir: Optional[Path | str] = None,
270+
mock: bool = False,
271+
overwrite_existing: bool = False,
272+
) -> logging.Logger | MockLogger:
248273
r"""Initialize a logger.
249274
250275
Parameters
251276
----------
252-
name : str
277+
name
253278
logger name (for subsequent calls to ``logging.getLogger``) to
254279
write to the same log file)
255280
256-
filename : str, optional
281+
filename
257282
filename to write log to. If not specified, filename will be
258283
the same as ``name`` with the extension ``log``
259284
260-
level : str, optional
261-
one of ``{critical, error, warning, info, debug, notset}``,
262-
case-insensitive
285+
level
286+
https://docs.python.org/3/library/logging.html#levels
263287
264-
log_dir : str, optional
288+
log_dir
265289
266-
mock : bool, optional
290+
mock
267291
if ``True``, return a ``CPAC.utils.monitoring.MockLogger``
268292
instead of a ``logging.Logger``
269293
270294
Returns
271295
-------
272-
logger : logging.Handler
273-
initialized logging Handler
296+
logger
297+
initialized logger
274298
275299
Examples
276300
--------
@@ -295,19 +319,25 @@ def set_up_logger(
295319
"""
296320
if filename is None:
297321
filename = f"{name}.log"
298-
try:
299-
level = getattr(logging, level.upper())
300-
except AttributeError:
322+
if isinstance(level, str):
323+
try:
324+
level = getattr(logging, level.upper())
325+
except AttributeError:
326+
pass
327+
if not level:
301328
level = logging.NOTSET
302-
if log_dir is None:
303-
log_dir = os.getcwd()
304-
filepath = os.path.join(log_dir, filename)
305-
if overwrite_existing and os.path.exists(filepath):
306-
with open(filepath, "w") as log_file:
329+
log_dir = Path(log_dir) if log_dir else Path.cwd()
330+
filepath = log_dir / filename
331+
if overwrite_existing and filepath.exists():
332+
with filepath.open("w", encoding="utf-8") as log_file:
307333
log_file.write("")
334+
if not filepath.exists():
335+
filepath.parent.mkdir(parents=True, exist_ok=True)
308336
if mock:
309337
return MockLogger(name, filename, level, log_dir)
310338
logger = getLogger(name)
339+
if isinstance(logger, MockLogger):
340+
return logger
311341
logger.setLevel(level)
312342
handler = logging.FileHandler(filepath)
313343
logger.addHandler(handler)

0 commit comments

Comments
 (0)