5151Pyro4 .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
5871class 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
212236def 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):
387414def __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