Skip to content

Commit cdcc916

Browse files
committed
logging:
* run_main, run_cli: * Add options log_file and append to support file logging directly * Add option stream to allow the output stream for console logging to be configured * add_file_logger: Auto-create parent directories of the log file if necessary
1 parent 9136d88 commit cdcc916

2 files changed

Lines changed: 31 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
* `util.logging`:
1111
* `LoggerContext` (and subclasses): Make sure any exceptions occurring within the context are logged
1212
* `FileLoggerContext`, `add_file_handler`: Add option `encoding` (which defaults to 'utf-8' on all platforms)
13+
* `run_main`, `run_cli`:
14+
* Add options `log_file` and `append` to support file logging directly
15+
* Add option `stream` to allow the output stream for console logging to be configured
16+
* `add_file_logger`: Auto-create parent directories of the log file if necessary
1317

1418
### Fixes
1519

src/sensai/util/logging.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,12 @@ def configure(format=LOG_DEFAULT_FORMAT, level=lg.DEBUG, stream=sys.stdout):
9999

100100

101101
# noinspection PyShadowingBuiltins
102-
def run_main(main_fn: Callable[..., T], format=LOG_DEFAULT_FORMAT, level=lg.DEBUG) -> T:
102+
def run_main(main_fn: Callable[..., T],
103+
format=LOG_DEFAULT_FORMAT,
104+
level=lg.DEBUG,
105+
logfile: Optional[str] = None,
106+
append: bool = True,
107+
stream=sys.stdout) -> T:
103108
"""
104109
Configures logging with the given parameters, ensuring that any exceptions that occur during
105110
the execution of the given function are logged.
@@ -108,9 +113,15 @@ def run_main(main_fn: Callable[..., T], format=LOG_DEFAULT_FORMAT, level=lg.DEBU
108113
:param main_fn: the function to be executed
109114
:param format: the log message format
110115
:param level: the minimum log level
116+
:param logfile: the path of a file to write logs to (in addition to console outputs);
117+
directories will be created as needed
118+
:param append: whether to append to the log file if it already exists
119+
:param stream: the output stream for console log messages
111120
:return: the result of `main_fn`
112121
"""
113-
configure(format=format, level=level)
122+
configure(format=format, level=level, stream=stream)
123+
if logfile is not None:
124+
add_file_logger(logfile, append=append)
114125
log.info("Starting")
115126
try:
116127
result = main_fn()
@@ -121,7 +132,12 @@ def run_main(main_fn: Callable[..., T], format=LOG_DEFAULT_FORMAT, level=lg.DEBU
121132

122133

123134
# noinspection PyShadowingBuiltins
124-
def run_cli(main_fn: Callable[..., T], format: str = LOG_DEFAULT_FORMAT, level: int = lg.DEBUG) -> Optional[T]:
135+
def run_cli(main_fn: Callable[..., T],
136+
format: str = LOG_DEFAULT_FORMAT,
137+
level: int = lg.DEBUG,
138+
logfile: Optional[str] = None,
139+
append: bool = True,
140+
stream = sys.stdout) -> Optional[T]:
125141
"""
126142
Configures logging with the given parameters and runs the given main function as a
127143
CLI using `jsonargparse` (which is configured to also parse attribute docstrings, such
@@ -133,12 +149,14 @@ def run_cli(main_fn: Callable[..., T], format: str = LOG_DEFAULT_FORMAT, level:
133149
:param main_fn: the function to be executed
134150
:param format: the log message format
135151
:param level: the minimum log level
152+
:param logfile: path of a file to write logs to (in addition to console outputs);
153+
directories will be created as needed
136154
:return: the result of `main_fn`
137155
"""
138156
from jsonargparse import set_docstring_parse_options, CLI
139157

140158
set_docstring_parse_options(attribute_docstrings=True)
141-
return run_main(lambda: CLI(main_fn), format=format, level=level)
159+
return run_main(lambda: CLI(main_fn), format=format, level=level, logfile=logfile, append=append, stream=stream)
142160

143161

144162
def datetime_tag() -> str:
@@ -161,14 +179,17 @@ def add_file_logger(path, append=True, register_atexit=True, encoding=LOG_FILE_D
161179
"""
162180
Adds a file logger which logs to the given path.
163181
164-
:param path: the path to the log file
182+
:param path: the path to the log file; directories will be created as needed
165183
:param append: whether to append in case the file already exists
166184
:param register_atexit: whether to register an atexit handler which reports the path to the log file upon program termination
167185
:param encoding: the encoding to use for the log file
168186
:return: the created file handler
169187
"""
170188
global _isAtExitReportFileLoggerRegistered
171189
log.info(f"Logging to {path} ...")
190+
dirname = os.path.dirname(path)
191+
if dirname and not os.path.exists(dirname):
192+
os.makedirs(dirname)
172193
mode = "a" if append else "w"
173194
handler = FileHandler(path, mode=mode, encoding=encoding)
174195
handler.setFormatter(Formatter(_logFormat))
@@ -397,7 +418,7 @@ class FileLoggerContext(LoggerContext[FileHandler]):
397418

398419
def __init__(self, path: str, append=True, enabled=True, encoding=LOG_FILE_DEFAULT_ENCODING):
399420
"""
400-
:param path: the path to the log file
421+
:param path: the path to the log file; directories will be created as needed
401422
:param append: whether to append in case the file already exists; if False, always create a new file.
402423
:param enabled: whether to actually perform any logging.
403424
This switch allows the with statement to be applied regardless of whether logging shall be enabled.

0 commit comments

Comments
 (0)