-
Notifications
You must be signed in to change notification settings - Fork 240
/
logger_utils.py
226 lines (186 loc) · 6.28 KB
/
logger_utils.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
"""Miscellaneous utilities related to logging."""
import sys
import logging
# If any method that creates handlers is called twice (e.g., setup reconfigure or during tests),
# then we need to prevent another one from being created. Since we have multiple loggers now, we
# store them in a dictionary.
_loggers = {}
def _set_handler(logger, handler, level, use_format):
"""
Set the StreamHandler for logger.
Parameters
----------
logger : object
Logger object.
handler : logging handler
handler to add to the logger
level : int
Logging level for this logger. Default is logging.INFO (level 20).
use_format : bool
Set to True to use the openmdao format "Level: message".
"""
# set a format which is simpler for console use
if use_format:
formatter = logging.Formatter('%(levelname)s: %(message)s')
handler.setFormatter(formatter)
handler.setLevel(level)
logger.addHandler(handler)
def get_logger(name='default_logger', level=logging.INFO, use_format=False,
out_stream='stdout', out_file=None, lock=None):
"""
Return a logger that writes to an I/O stream.
Parameters
----------
name : str
Name of the logger to be returned, will be created if it doesn't exist.
level : int
Logging level for this logger. Default is logging.INFO (level 20).
(applied only when creating a new logger or setting a new stream).
use_format : bool
Set to True to use the openmdao format "Level: message".
(applied only when creating a new logger or setting a new stream).
out_stream : 'stdout', 'stderr' or file-like
output stream to which logger output will be directed.
out_file : str or None
If not None, add a FileHandler to write to this file.
lock : bool
if True, do not allow the handler to be changed until unlocked.
if False, unlock the handler for the logger.
Returns
-------
<logging.Logger>
Logger that writes to a stream and adheres to requested settings.
"""
if out_stream == 'stdout':
out_stream = sys.stdout
elif out_stream == 'stderr':
out_stream = sys.stderr
if name in _loggers:
# use existing logger
info = _loggers[name]
logger = info['logger']
stream = info['stream']
ofile = info['file']
locked = info['locked']
unlock = lock is False
# redirect log to new stream (if not locked)
if (out_stream != stream or ofile != out_file) and (not locked or unlock):
for handler in logger.handlers:
logger.removeHandler(handler)
if out_stream:
_set_handler(logger, logging.StreamHandler(out_stream), level, use_format)
if out_file:
_set_handler(logger, logging.FileHandler(out_file, mode='w'), level, use_format)
info['stream'] = out_stream
info['file'] = out_file
# update locked status
info['locked'] = lock
else:
# create new logger
logger = logging.getLogger(name)
logger.setLevel(level)
if out_stream:
_set_handler(logger, logging.StreamHandler(out_stream), level, use_format)
if out_file:
_set_handler(logger, logging.FileHandler(out_file, mode='w'), level, use_format)
_loggers[name] = {
'logger': logger,
'stream': out_stream,
'file': out_file,
'locked': lock
}
return logger
class TestLogger(object):
"""
A logger replacement for testing that simplifies checking log output.
Attributes
----------
_msgs : dict
Stores lists of messages under 'error', 'warning' and 'info' keys.
"""
def __init__(self):
"""
Initialize the message dict.
"""
self._msgs = {'error': [], 'warning': [], 'info': []}
def error(self, msg):
"""
Collect an error message.
Parameters
----------
msg : str
An error message.
"""
self._msgs['error'].append(msg)
def warning(self, msg):
"""
Collect a warning message.
Parameters
----------
msg : str
A warning message.
"""
self._msgs['warning'].append(msg)
def info(self, msg):
"""
Collect an informational message.
Parameters
----------
msg : str
An informational message.
"""
self._msgs['info'].append(msg)
def debug(self, msg):
"""
Collect a debug message.
Parameters
----------
msg : str
A debugging message.
"""
self._msgs['debug'].append(msg)
def get(self, typ):
"""
Return all stored messages of a specific type.
Parameters
----------
typ : str
Type of messages ('error', 'warning', 'info') to be returned.
Returns
-------
list of str
Any messages of that type that have been written to the logger.
"""
return self._msgs[typ]
def contains(self, typ, message):
"""
Do any of the stored messages of a specific type equal the given message.
Parameters
----------
typ : str
Type of messages ('error', 'warning', 'info') to be returned.
message : str
The message to match.
Returns
-------
bool
True if any of the lines of stored messages of a specific type equal the line.
"""
for s in self._msgs[typ]:
if s == message:
return True
return False
def find_in(self, typ, message):
"""
Find the given message among the stored messages.
Raises an exception if the given message isn't found.
Parameters
----------
typ : str
Type of messages ('error', 'warning', 'info') to be searched.
message : str
The message to match.
"""
if not self.contains(typ, message):
raise RuntimeError('Message "{}" not found in {}.'.format(message,
',\n'.join(self._msgs[typ])))