Skip to content

Commit e71328c

Browse files
committed
deviceserver: handle log records from all loggers (issue #110)
Only log records from the deviceserver module logger and the individual device loggers were being handled, i.e. appear on stderr and on the device server log files. Log records from other loggers, such as Pyro, pySerial, got discarded. Even other microscope loggers would get discarded which is why there is no other loggers in microscope. This commit changes the deviceserver program so that it configures the root logger in order to handle all log records. We still get separate log files per device by reconfiguring the root logger in the new process. We also get to identify the device emitting the messages on stderr by using a log record formatter that includes the device name in addition to the logger name (these two are the same when the log record comes from the device logger).
1 parent d9cb040 commit e71328c

File tree

1 file changed

+61
-31
lines changed

1 file changed

+61
-31
lines changed

microscope/deviceserver.py

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,22 @@
5151
Pyro4.config.REQUIRE_EXPOSE = False
5252

5353

54-
# Logging formatter.
55-
LOG_FORMATTER = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:'
56-
'PID %(process)s: %(message)s')
54+
def _create_log_formatter(name: str):
55+
"""Create a logging.Formatter for the device server.
56+
57+
Each device is served on its own process and each device has its
58+
own log file. But the logs from all device servers also appear on
59+
stderr where it will be difficult to figure out from which device
60+
server a log message comes. This creates a logging.Formatter
61+
which includes the device server name.
62+
63+
Args:
64+
name (str): device name to be used on the log output.
65+
66+
"""
67+
return logging.Formatter('%%(asctime)s:%s (%%(name)s):%%(levelname)s'
68+
':PID %%(process)s: %%(message)s' % name)
69+
5770

5871
class Filter(logging.Filter):
5972
def __init__(self):
@@ -122,20 +135,37 @@ def clone(self):
122135
self._id_to_port, exit_event=self.exit_event)
123136

124137
def run(self):
125-
logger = logging.getLogger(self._device_def['cls'].__name__)
138+
cls_name = self._device_def['cls'].__name__
139+
logger = logging.getLogger(cls_name)
140+
141+
# If the multiprocessing start method is fork, the child
142+
# process gets a copy of the root logger. The copy is
143+
# configured to sign the messages as "device-server", and
144+
# write to the main log file and stderr. We remove those
145+
# handlers so that this DeviceServer is logged to a separate
146+
# file and the messages are signed with the device name.
147+
root_logger = logging.getLogger()
148+
# Get a new list of handlers because otherwise we are
149+
# iterating over the same list as removeHandler().
150+
for handler in list(root_logger.handlers):
151+
root_logger.removeHandler(handler)
152+
126153
if __debug__:
127-
logger.setLevel(logging.DEBUG)
154+
root_logger.setLevel(logging.DEBUG)
128155
else:
129-
logger.setLevel(logging.INFO)
156+
root_logger.setLevel(logging.INFO)
157+
158+
130159
# Later, we'll log to one file per server, with a filename
131160
# based on a unique identifier for the device. Some devices
132161
# don't have UIDs available until after initialization, so
133162
# log to stderr until then.
134163
stderr_handler = StreamHandler(sys.stderr)
135-
stderr_handler.setFormatter(LOG_FORMATTER)
136-
logger.addHandler(stderr_handler)
137-
logger.addFilter(Filter())
138-
logger.debug("Debugging messages on.")
164+
stderr_handler.setFormatter(_create_log_formatter(cls_name))
165+
root_logger.addHandler(stderr_handler)
166+
root_logger.debug("Debugging messages on.")
167+
168+
root_logger.addFilter(Filter())
139169

140170
self._device = self._device_def['cls'](**self._device_def['conf'])
141171
while not self.exit_event.is_set():
@@ -158,14 +188,15 @@ def run(self):
158188
host = self._device_def['host']
159189
port = self._device_def['port']
160190
pyro_daemon = Pyro4.Daemon(port=port, host=host)
161-
log_handler = RotatingFileHandler("%s_%s_%s.log" %
162-
(type(self._device).__name__,
163-
host, port))
164-
log_handler.setFormatter(LOG_FORMATTER)
165-
logger.addHandler(log_handler)
191+
192+
log_handler = RotatingFileHandler('%s_%s_%s.log'
193+
% (cls_name, host, port))
194+
log_handler.setFormatter(_create_log_formatter(cls_name))
195+
root_logger.addHandler(log_handler)
196+
166197
logger.info('Device initialized; starting daemon.')
167198

168-
pyro_daemon.register(self._device, type(self._device).__name__)
199+
pyro_daemon.register(self._device, cls_name)
169200
if isinstance(self._device, microscope.devices.ControllerDevice):
170201
# AUTOPROXY should be enabled by default. If we find it
171202
# disabled, there must be a reason why, so raise an error
@@ -178,13 +209,6 @@ def run(self):
178209
Pyro4.config.SERIALIZERS_ACCEPTED.discard('marshal')
179210

180211
for sub_device in self._device.devices.values():
181-
# FIXME: by the time we do this the device has already
182-
# been created and initialised and that won't be
183-
# logged. We need to rethink having a log per device
184-
# (issue #110)
185-
sub_device._logger.addHandler(stderr_handler)
186-
sub_device._logger.addHandler(log_handler)
187-
sub_device._logger.addFilter(Filter())
188212
pyro_daemon.register(sub_device)
189213

190214
# Run the Pyro daemon in a separate thread so that we can do
@@ -211,10 +235,13 @@ def run(self):
211235

212236
def serve_devices(devices, exit_event=None):
213237
logger = logging.getLogger(__name__)
238+
root_logger = logging.getLogger()
239+
214240
log_handler = RotatingFileHandler("__MAIN__.log")
215-
log_handler.setFormatter(LOG_FORMATTER)
216-
logger.addHandler(log_handler)
217-
logger.setLevel(logging.DEBUG)
241+
log_handler.setFormatter(_create_log_formatter('device-server'))
242+
root_logger.addHandler(log_handler)
243+
244+
root_logger.setLevel(logging.DEBUG)
218245

219246
# An event to trigger clean termination of subprocesses. This is the
220247
# only way to ensure devices are shut down properly when processes
@@ -387,14 +414,17 @@ def validate_devices(configfile):
387414
def __console__():
388415
"""Serve devices from a console process."""
389416
logger = logging.getLogger(__name__)
417+
root_logger = logging.getLogger()
390418
if __debug__:
391-
logger.setLevel(logging.DEBUG)
419+
root_logger.setLevel(logging.DEBUG)
392420
else:
393-
logger.setLevel(logging.INFO)
421+
root_logger.setLevel(logging.INFO)
422+
394423
stderr_handler = StreamHandler(sys.stderr)
395-
stderr_handler.setFormatter(LOG_FORMATTER)
396-
logger.addHandler(stderr_handler)
397-
logger.addFilter(Filter())
424+
stderr_handler.setFormatter(_create_log_formatter('device-server'))
425+
root_logger.addHandler(stderr_handler)
426+
427+
root_logger.addFilter(Filter())
398428

399429
if len(sys.argv) < 2:
400430
logger.critical("No config file specified. Exiting.")

0 commit comments

Comments
 (0)