angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Registro de la aplicación Python con Docker

Las mejores prácticas de Docker sugieren que se registre en stdout , pero hay algunos inconvenientes.

5 diciembre 2022
post main image
https://unsplash.com/@agk42

Cuando desarrolle una aplicación de software, probablemente lo primero que configure sea el registro. Primero sólo a la consola, pero pronto añadirás archivos de registro.

Hace algunos años empecé a usar Docker para desarrollo y producción. No hice muchos cambios al pasar a Docker. Los archivos de registro de la aplicación siguen estando en un directorio de registro en un volumen Docker . Esto significa que los archivos de registro son parte de la aplicación framework.

Las mejores prácticas en Internet sugieren utilizar el registro Docker . Esto significa que nuestra aplicación Docker debe imprimir los registros de bitácora a stdout (y/o sterr), en lugar de escribirlos en los archivos de bitácora de nuestra aplicación. Cuando enviamos nuestros registros de registro a stdout en Docker, podemos especificar un controlador de registro para exportarlos a una herramienta externa como Syslog, utilizando los controladores de registro suministrados por Docker o los controladores de registro de terceros. Es justo, entiendo que esto puede tener ventajas, o incluso es esencial.

De nuevo, no quiero cambiar demasiado y quedarme con el driver de registro por defecto en Docker: json-file. Nuestros registros de registro se presentarán en los archivos:

/var/lib/docker/containers/<container id>/<container id>-json.log

Uno de mis proyectos actuales de Python es una aplicación que consiste en un montón de (micro) servicios. Un servicio está representado por un contenedor con los archivos de registro todavía en el contexto / framework del servicio. En este post convierto el método de registro de mi aplicación existente a Docker logging.

Cambiando el módulo de logging

Para mis aplicaciones desarrollé un módulo de logging personalizado, dentro de este módulo están los métodos de logging estándar Python , lo que significa que el código de mi aplicación tiene líneas como:

logger.debug(...)
logger.info(...)
logger.error(...)

Estoy usando este módulo de registro en todas partes. Esto significa que sólo tuve que añadir un nuevo modo de registro, utilizando una variable, use_docker_logging=True, que indica que se escriban los datos de registro en stdout en lugar de en un archivo.

Podemos hacer esto muy fácilmente usando logging.StreamHandler(stdout). Cuando se utiliza el módulo de registro Python , los datos se vacían después de cada registro. Esto significa que no es necesario iniciar Python como 'python -u' o utilizar la variable de entorno PYTHONUNBUFFERED.

Docker Exec, problemas, ¿dónde están mis registros?

Por desgracia, no hemos terminado, hay un problema. No sé ustedes, pero yo hago todo mi desarrollo utilizando contenedores Docker hoy en día. En muchos casos tengo el comando en el archivo docker-compose.yml como:

command: tail -f /dev/null

Esto significa, iniciar el contenedor y mantenerlo vivo. Entonces yo 'Docker Exec' en el contenedor (shell), y ejecutar los scripts Python . Cuando hice esto, los registros de la sesión 'Docker Exec' no aparecieron en los registros de Docker .

Después de buscar en internet he encontrado esta página (Docker issues) 'Proposal: additional logging options for docker exec', ver enlaces más abajo.

Parece que cuando 'Docker Exec' en un contenedor, el stdout de esta sesión no es el stdout de la sesión inicial. Habrá razones para esto, no podemos cambiarlo, y tenemos que lidiar con esto.

La solución es redirigir los registros de la sesión a:

/proc/1/fd/1

Entonces encontré otra (Docker cuestiones) página en Internet 'Echoing to /dev/stdout does not appear in docker logs', ver enlaces más abajo. Una de las sugerencias es que se registre en un archivo de enlace simbólico. Creamos el symlink en el Dockerfile:

RUN ln -sf /proc/1/fd/1 /var/log/test.log

Volviendo a la solución que teníamos hasta ahora, sustituimos el logging.StreamHandler por el logging.FileHandler:

logging.FileHandler('/var/log/test.log')

Ahora los registros de logs de los scripts Python que se ejecutan en la sesión 'Docker Exec', aparecen en los logs Docker .

Sesión Docker Exec, sin registro en la consola

Desafortunadamente, no hemos terminado, hay otro problema. Debido a que utilizamos el logging.FileHandler, sólo registramos en un archivo, stdout, siendo los registros del Docker . Para ver los registros en la pantalla en la sesión 'Docker Exec', debemos añadir el logging.StreamHandler de nuevo.

Pero espera, sólo debemos hacer esto para la sesión 'Docker Exec', de lo contrario veremos registros duplicados en los logs de Docker .

He resuelto esto de una forma un poco complicada, encontrando el nombre del proceso padre superior. Si este nombre de proceso es 'sh' o 'bash' entonces asumo que usamos Docker Exec para entrar en el contenedor.

import psutil

    ...
    # get 'top' parent process of this docker exec session
    parent_process_pid = os.getpid()
    parent_process_name = None
    while True:
        #print('parent_process_pid = {}'.format(parent_process_pid))
        parent_process = psutil.Process(parent_process_pid)
        pid = parent_process.ppid()
        parent_process_name = parent_process.name()
        if pid == 0:
            break
        parent_process_pid = pid

    if parent_name in ['sh', 'bash']:
        # add console logging
        ...
    else:
        # no console logging
        ...
    ...

Líneas de registro con campos extra

Todos los registros de un contenedor están ahora en un único archivo de registro. Si tienes varios servicios (procesos) ejecutándose en un contenedor, probablemente quieras añadir campos extra a las líneas de registro que identifican el servicio (proceso). Esto además del campo logging.DEBUG, logging.ERROR etc. que insertamos en las líneas de registro.

En el registro Python podemos añadir un campo extra a una línea de registro con el método logging.setLogRecordFactory() . Se ofrece un ejemplo en la página 'Using LogRecordFactory in python to add custom fields for logging', ver enlaces más abajo.

Otros cambios

Tenga en cuenta que con el controlador de registro Docker por defecto sólo podemos escribir una cadena en stdout, escribir un objeto JSON (diccionario) no es posible. Una severa limitación, pero tenemos que vivir con ella.

Si queremos añadir etiquetas a todos (!) los registros de bitácora, podemos usar etiquetas en Docker-Compose:

Ejemplo:

  some-service:
    image: ...
    ports:
      - "8082:8000"
    labels:
      log_for_application: "myapp"

Al registrar en Docker, la marca de tiempo es añadida automáticamente por Docker. Esto significa que debemos eliminarlo de nuestra línea de registro Python . Mi cadena final del formateador de registro tiene el siguiente aspecto:

'%(proc_id)-15.15s %(levelname)-8.8s [%(filename)-30s%(funcName)20s():%(lineno)03s] %(message)s'

Ver los registros Docker de todos los contenedores con Dozzle

Hay muchas soluciones para ver los registros de Docker de todos los contenedores. He probado algunas, y la más fácil de configurar y utilizar fue Dozzle. Dozzle es un visor de registros en tiempo real para contenedores Docker, ver los enlaces de abajo. Para ejecutarlo, sólo tienes que tirar del contenedor, y estás listo para ir. Dozzle se parece a Logs Explorer, una extensión de Docker Desktop, ver enlaces abajo.

Tenga cuidado, por defecto Dozzle le conecta a Google Analytics, asegúrese de iniciar Dozzle con la bandera '--no-analytics':

docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest --no-analytics

Una vez iniciado, dirija su navegador a:

http://127.0.0.1:8888

Con Dozzle es muy fácil ver sus registros de Docker .

La búsqueda utiliza regex, lo que significa que si, por ejemplo, quieres filtrar dos términos, 'Term1' y 'Term2',
puedes escribir en el cuadro de búsqueda:

Term1.*?Term2

Otra cosa buena con Dozzle es que podemos iniciarlo también para un solo contenedor, o unos cuantos contenedores. Lo que realmente se echa en falta, es una forma de almacenar y reseleccionar las búsquedas. Pero no me quejo. ¡Gran herramienta!

Resumen

Esto tomó mucho más tiempo de lo que esperaba. Fue mucho más que imprimir a stdout. ¿Merece la pena? Para la aplicación actual, que consiste en muchos servicios representados por contenedores, creo que sí.

Ahora que el registro se hace a través de Docker, puedo buscar otras formas de ver los registros, generar alertas, etc.

Enlaces / créditos

Docker - Configure logging drivers
https://docs.docker.com/config/containers/logging/configure

Dozzle, a real-time log viewer for docker containers
https://dozzle.dev

Echoing to /dev/stdout does not appear in 'docker logs' #19616
https://github.com/moby/moby/issues/19616

Proposal: additional logging options for docker exec #8662
https://github.com/moby/moby/issues/8662

Using LogRecordFactory in python to add custom fields for logging
https://stackoverflow.com/questions/59585861/using-logrecordfactory-in-python-to-add-custom-fields-for-logging

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.