-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathlogging.py
137 lines (113 loc) · 4.85 KB
/
logging.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""
PyFstat's logging implementation.
PyFstat main logger is called `pyfstat` and can be accessed via::
import logging
logger = logging.getLoger('pyfstat')
Basics of logging: For all our purposes, there are *logger* objects
and *handler* objects. Loggers are the ones in charge of logging,
hence you call them to emit a logging message with a specific logging
level (e.g. ``logger.info``); handlers are in charge of redirecting that
message to a specific place (e.g. a file or your terminal, which is
usually referred to as a *stream*).
The default behaviour upon importing ``pyfstat`` is to attach a ``logging.StreamHandler`` to
the *pyfstat* logger, printing out to ``sys.stdout``.
This is only done if the root logger has no handlers attached yet;
if it does have at least one handler already,
then we inherit those and do not add any extra handlers by default.
If, for any reason, ``logging`` cannot access ``sys.stdout`` at import time,
the exception is reported via ``print`` and no handlers are attached
(i.e. the logger won't print to ``sys.stdout``).
The user can modify the logger's behaviour at run-time using ``set_up_logger``.
This function attaches extra ``logging.StreamHandler`` and ``logging.FileHandler``
handlers to the logger, allowing to redirect logging messages to a different
stream or a specific output file specified using the ``outdir, label`` variables
(with the same format as in the rest of the package).
Finally, logging can be disabled, or the level changed, at run-time by
manually configuring the *pyfstat* logger. For example, the following block of code
will suppress logging messages below ``WARNING``::
import logging
logging.getLogger('pyfstat').setLevel(logging.WARNING)
"""
import logging
import os
import sys
from typing import TYPE_CHECKING, Iterable, Optional
if TYPE_CHECKING:
import io
def _get_default_logger() -> logging.Logger:
root_logger = logging.getLogger()
return root_logger if root_logger.handlers else set_up_logger()
def set_up_logger(
outdir: Optional[str] = None,
label: Optional[str] = "pyfstat",
log_level: str = "INFO", # FIXME: Requires Python 3.8 Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] = "INFO",
streams: Optional[Iterable["io.TextIOWrapper"]] = (sys.stdout,),
append: bool = True,
) -> logging.Logger:
"""Add file and stream handlers to the `pyfstat` logger.
Handler names generated from ``streams`` and ``outdir, label``
must be unique and no duplicated handler will be attached by
this function.
Parameters
----------
outdir:
Path to outdir directory. If ``None``, no file handler will be added.
label:
Label for the file output handler, i.e.
the log file will be called `label.log`.
Required, in conjunction with ``outdir``, to add a file handler.
Ignored otherwise.
log_level:
Level of logging. This level is imposed on the logger itself and
*every single handler* attached to it.
streams:
Stream to which logging messages will be passed using a
StreamHandler object. By default, log to ``sys.stdout``.
Other common streams include e.g. ``sys.stderr``.
append:
If ``False``, removes all handlers from the `pyfstat` logger
before adding new ones. This removal is not propagated to
handlers on the `root` logger.
Returns
-------
logger: logging.Logger
Configured instance of the ``logging.Logger`` class.
"""
logger = logging.getLogger("pyfstat")
logger.setLevel(log_level)
if not append:
for handler in logger.handlers:
logger.removeHandler(handler)
else:
for handler in logger.handlers:
handler.setLevel(log_level)
stream_names = [
handler.stream.name
for handler in logger.handlers
if isinstance(handler, logging.StreamHandler)
]
file_names = [
handler.baseFilename
for handler in logger.handlers
if isinstance(handler, logging.FileHandler)
]
common_formatter = logging.Formatter(
"%(asctime)s.%(msecs)03d %(name)s %(levelname)-8s: %(message)s",
datefmt="%y-%m-%d %H:%M:%S", # intended to match LALSuite's format
)
for stream in streams or []:
if stream.name in stream_names:
continue
stream_handler = logging.StreamHandler(stream)
stream_handler.setFormatter(common_formatter)
stream_handler.setLevel(log_level)
logger.addHandler(stream_handler)
if label and outdir:
os.makedirs(outdir, exist_ok=True)
log_file = os.path.join(outdir, f"{label}.log")
if log_file not in file_names:
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(common_formatter)
file_handler.setLevel(log_level)
logger.addHandler(file_handler)
return logger