paralelo - ¿Python logging admite el multiprocesamiento?
procesos concurrentes python (3)
Me han dicho que el registro no se puede utilizar en multiprocesamiento. Debe realizar el control de concurrencia en caso de que el multiprocesamiento complique el registro.
Pero hice algunas pruebas, parece que no hay ningún problema al usar el registro en multiprocesamiento
import time
import logging
from multiprocessing import Process, current_process, pool
# setup log
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG,
format=''%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s'',
datefmt=''%a, %d %b %Y %H:%M:%S'',
filename=''/tmp/test.log'',
filemode=''w'')
def func(the_time, logger):
proc = current_process()
while True:
if time.time() >= the_time:
logger.info(''proc name %s id %s'' % (proc.name, proc.pid))
return
if __name__ == ''__main__'':
the_time = time.time() + 5
for x in xrange(1, 10):
proc = Process(target=func, name=x, args=(the_time, logger))
proc.start()
Como se puede ver en el código.
Dejo deliberadamente que el subproceso escriba en el mismo momento (5s después del inicio) para aumentar la posibilidad de conflicto. Pero no hay conflicto en absoluto.
Entonces mi pregunta es ¿podemos usar el registro en multiprocesamiento? ¿Por qué tantas publicaciones dicen que no podemos?
Como Matino explicó correctamente: el inicio de sesión en una configuración de multiprocesamiento no es seguro, ya que múltiples procesos (que no saben nada acerca de los otros existentes) están escribiendo en el mismo archivo, y pueden intervenir entre ellos.
Ahora, lo que sucede es que cada proceso tiene un identificador de archivo abierto y hace una "escritura anexada" en ese archivo. La pregunta es en qué circunstancias la escritura del apéndice es "atómica" (es decir, no se puede interrumpir, por ejemplo, mediante otro proceso que se escriba en el mismo archivo y mezcle su salida). Este problema se aplica a todos los lenguajes de programación, ya que al final harán un syscall al kernel. Esta respuesta responde en qué circunstancias un archivo de registro compartido está bien.
Todo se reduce a comprobar el tamaño del búfer de su tubería, en linux que se define en /usr/include/linux/limits.h
y tiene 4096 bytes. Para otros sistemas operativos encuentras here una buena lista.
Eso significa: si su línea de registro es inferior a 4''096 bytes (si está en Linux), entonces el anexo es seguro, si el disco está conectado directamente (es decir, no hay una red entre ellos). Pero para más detalles, por favor revise el primer enlace en mi respuesta. Para probar esto, puede hacer logger.info(''proc name %s id %s %s'' % (proc.name, proc.pid, str(proc.name)*5000))
con diferentes longitudes. Con 5000, por ejemplo, ya he mezclado las líneas de registro en /tmp/test.log
.
En esta pregunta ya hay bastantes soluciones para esto, así que no agregaré mi propia solución aquí.
Actualización: Matraz y multiprocesamiento.
Los frameworks web como flask se ejecutarán en varios trabajadores si son alojados por uwsgi o nginx. En ese caso, varios procesos pueden escribir en un archivo de registro. ¿Tendrá problemas?
El manejo de errores en el matraz se realiza a través de stdout / stderr, que luego es capturado por el servidor web (uwsgi, nginx, etc.) que debe tener cuidado de que los registros se escriban de manera correcta (ver por ejemplo [este matraz + nginx ejemplo])) http://flaviusim.com/blog/Deploying-Flask-with-nginx-uWSGI-and-Supervisor/ ), probablemente también agregue información del proceso para que pueda asociar las líneas de error a los procesos. De los matraces doc :
De forma predeterminada a partir de Flask 0.11, los errores se registran automáticamente en el registro de su servidor web. Las advertencias sin embargo no lo son.
Por lo tanto, aún tendría este problema de archivos de registro entremezclados si usa warn
y el mensaje excede el tamaño del búfer de tubería.
No es seguro escribir en un solo archivo desde varios procesos.
De acuerdo a https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes
Aunque el registro es seguro para subprocesos y se admite el registro en un solo archivo desde varios subprocesos en un solo proceso, no se admite el registro en un solo archivo desde varios procesos, porque no hay una manera estándar de serializar el acceso a un solo archivo en varios Procesos en Python.
Una posible solución sería hacer que cada proceso escriba en su propio archivo. Puede lograr esto escribiendo su propio controlador que agrega el proceso pid al final del archivo:
import logging.handlers
import os
class PIDFileHandler(logging.handlers.WatchedFileHandler):
def __init__(self, filename, mode=''a'', encoding=None, delay=0):
filename = self._append_pid_to_filename(filename)
super(PIDFileHandler, self).__init__(filename, mode, encoding, delay)
def _append_pid_to_filename(self, filename):
pid = os.getpid()
path, extension = os.path.splitext(filename)
return ''{0}-{1}{2}''.format(path, pid, extension)
Entonces solo necesitas llamar a addHandler
:
logger = logging.getLogger(''foo'')
fh = PIDFileHandler(''bar.log'')
logger.addHandler(fh)
Use una cola para el manejo correcto de la concurrencia que se recupera simultáneamente de los errores al enviar todo al proceso principal a través de una canalización.
from logging.handlers import RotatingFileHandler
import multiprocessing, threading, logging, sys, traceback
class MultiProcessingLog(logging.Handler):
def __init__(self, name, mode, maxsize, rotate):
logging.Handler.__init__(self)
self._handler = RotatingFileHandler(name, mode, maxsize, rotate)
self.queue = multiprocessing.Queue(-1)
t = threading.Thread(target=self.receive)
t.daemon = True
t.start()
def setFormatter(self, fmt):
logging.Handler.setFormatter(self, fmt)
self._handler.setFormatter(fmt)
def receive(self):
while True:
try:
record = self.queue.get()
self._handler.emit(record)
except (KeyboardInterrupt, SystemExit):
raise
except EOFError:
break
except:
traceback.print_exc(file=sys.stderr)
def send(self, s):
self.queue.put_nowait(s)
def _format_record(self, record):
# ensure that exc_info and args
# have been stringified. Removes any chance of
# unpickleable things inside and possibly reduces
# message size sent over the pipe
if record.args:
record.msg = record.msg % record.args
record.args = None
if record.exc_info:
dummy = self.format(record)
record.exc_info = None
return record
def emit(self, record):
try:
s = self._format_record(record)
self.send(s)
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
def close(self):
self._handler.close()
logging.Handler.close(self)
El controlador realiza toda la escritura del archivo del proceso principal y utiliza solo un hilo para recibir los mensajes pasados de los procesos secundarios