Python Anwendungsprotokollierung mit Docker
Docker Best Practices empfehlen die Protokollierung in stdout , aber es gibt einige Probleme.
Wenn Sie eine Softwareanwendung entwickeln, werden Sie wahrscheinlich als erstes die Protokollierung einrichten. Zunächst nur auf der Konsole, aber bald werden Sie Protokolldateien hinzufügen.
Vor einigen Jahren begann ich, Docker für die Entwicklung und Produktion zu verwenden. Bei der Umstellung auf Docker habe ich keine großen Änderungen vorgenommen. Die Anwendungsprotokolldateien befinden sich immer noch in einem Protokollverzeichnis auf einem Docker Volume. Dies bedeutet, dass die Protokolldateien Teil der Anwendung framework sind.
Best Practices im Internet empfehlen die Verwendung von Docker Logging. Das bedeutet, dass unsere Docker -Anwendung Protokollsätze an stdout (und/oder sterr) ausgeben muss, anstatt sie in unsere Anwendungsprotokolldateien zu schreiben. Wenn wir unsere Protokolldatensätze in Docker an stdout senden, können wir einen Protokolltreiber angeben, um sie in ein externes Tool wie Syslog zu exportieren, indem wir von Docker bereitgestellte Protokolltreiber oder Protokolltreiber von Drittanbietern verwenden. Nun gut, ich verstehe, dass dies Vorteile haben kann oder sogar unerlässlich ist.
Auch hier möchte ich nicht zu viel ändern und bleibe bei dem Standard-Protokolltreiber in Docker: json-file. Unsere Log-Aufzeichnungen werden in den Dateien vorhanden sein:
/var/lib/docker/containers/<container id>/<container id>-json.log
Eines meiner aktuellen Python -Projekte ist eine Anwendung, die aus vielen (Mikro-)Services besteht. Ein Service wird durch einen Container repräsentiert, wobei die Logdateien noch im Kontext / framework des Services stehen. In diesem Beitrag konvertiere ich meine bestehende Anwendungslogging-Methode in Docker -Logging.
Ändern des Logging-Moduls
Für meine Anwendungen habe ich ein benutzerdefiniertes Logging-Modul entwickelt. Innerhalb dieses Moduls befinden sich die Standard Python Logging-Methoden, d.h. mein Anwendungscode enthält Zeilen wie:
logger.debug(...)
logger.info(...)
logger.error(...)
Ich verwende dieses Protokollierungsmodul überall. Das bedeutet, dass ich nur einen neuen Protokollierungsmodus hinzufügen musste, der eine Variable, use_docker_logging=True, verwendet, die angibt, dass die Protokolldaten in stdout statt in eine Datei geschrieben werden sollen.
Wir können dies sehr einfach mit logging.StreamHandler(stdout) tun. Bei Verwendung des Protokollierungsmoduls Python werden die Daten nach jedem Datensatz gespült. Das bedeutet, daß es nicht notwendig ist, Python als 'python -u' zu starten oder die Umgebungsvariable PYTHONUNBUFFERED zu verwenden.
Docker Exec, Probleme, wo sind meine Protokollsätze?
Leider sind wir noch nicht fertig, es gibt ein Problem. Ich weiß nicht, wie es Ihnen geht, aber ich verwende heute für meine gesamte Entwicklung Docker -Container. In vielen Fällen habe ich den Befehl in der Datei docker-compose.yml wie:
command: tail -f /dev/null
Das heißt, starte den Container und halte ihn am Leben. Dann gebe ich 'Docker Exec' in den Container (Shell) ein und führe die Python -Skripte aus. Als ich dies tat, erschienen die Log-Einträge der 'Docker Exec'-Sitzung nicht in den Docker -Logs.
Nach einer Suche im Internet fand ich diese (Docker issues) Seite 'Proposal: additional logging options for docker exec', siehe Links unten.
Es scheint, dass, wenn Sie 'Docker Exec' in einen Container ausführen, die stdout dieser Sitzung nicht die stdout der ursprünglichen Sitzung ist. Dafür wird es Gründe geben, wir können es nicht ändern, und wir müssen damit umgehen.
Die Lösung besteht darin, die Protokolldatensätze nach umzuleiten:
/proc/1/fd/1
Dann fand ich eine andere (Docker issues) Seite im Internet 'Echoing to /dev/stdout does not appear in docker logs', siehe Links unten. Einer der Vorschläge ist die Protokollierung in einer sym-verknüpften Datei. Wir erstellen die symlink in der Dockerfile:
RUN ln -sf /proc/1/fd/1 /var/log/test.log
Zurück zu unserer bisherigen Lösung, ersetzen wir die logging.StreamHandler durch die logging.FileHandler:
logging.FileHandler('/var/log/test.log')
Jetzt erscheinen die Protokollsätze von Python -Skripten, die in der Sitzung "Docker Exec" ausgeführt werden, in den Docker -Protokollen.
Docker Exec-Sitzung, keine Konsolenprotokollierung
Leider sind wir noch nicht fertig, es gibt ein weiteres Problem. Da wir den logging.FileHandler verwendet haben, protokollieren wir nur in einer Datei, stdout, die die Docker -Logs sind. Um die Protokollsätze auf dem Bildschirm in der Sitzung "Docker Exec" zu sehen, müssen wir die Datei logging.StreamHandler wieder hinzufügen.
Aber halt, das müssen wir nur für die "Docker Exec"-Sitzung tun, sonst sehen wir doppelte Protokollsätze in den Docker -Protokollen.
Ich habe dieses Problem auf eine etwas umständliche Weise gelöst, indem ich den Namen des obersten übergeordneten Prozesses ermittelt habe. Wenn dieser Prozessname 'sh' oder 'bash' lautet, gehe ich davon aus, dass wir Docker Exec verwendet haben, um den Container zu betreten.
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
...
...
Protokollzeilen mit zusätzlichen Feldern
Alle Log-Einträge eines Containers befinden sich jetzt in einer einzigen Log-Datei. Wenn Sie mehrere Dienste (Prozesse) in einem Container laufen haben, möchten Sie wahrscheinlich zusätzliche Felder zu den Protokollzeilen hinzufügen, die den Dienst (Prozess) identifizieren. Dies geschieht zusätzlich zu den Feldern logging.DEBUG, logging.ERROR usw., die wir in die Protokollzeilen einfügen.
In der Python -Protokollierung können wir mit der Methode logging.setLogRecordFactory() ein zusätzliches Feld in eine Protokollzeile einfügen. Ein Beispiel wird auf der Seite 'Using LogRecordFactory in python to add custom fields for logging' gegeben, siehe Links unten.
Andere Änderungen
Beachten Sie, dass mit dem Standard Docker Logging-Treiber nur ein String in stdout geschrieben werden kann, das Schreiben eines JSON-Objekts (Dictionary) ist nicht möglich. Eine schwerwiegende Einschränkung, aber wir müssen damit leben.
Wenn wir allen (!) Protokolleinträgen Tags hinzufügen wollen, können wir Labels in Docker-Compose verwenden:
Beispiel:
some-service:
image: ...
ports:
- "8082:8000"
labels:
log_for_application: "myapp"
Beim Loggen in Docker wird der Zeitstempel automatisch von Docker hinzugefügt. Das bedeutet, dass wir ihn aus unserer Python -Logzeile entfernen müssen. Meine endgültige Logger-Formatierungszeichenfolge sieht so aus:
'%(proc_id)-15.15s %(levelname)-8.8s [%(filename)-30s%(funcName)20s():%(lineno)03s] %(message)s'
Anzeige der Docker -Protokolle aller Container mit Dozzle
Es gibt viele Lösungen, um die Docker -Protokolle aller Container anzuzeigen. Ich habe einige ausprobiert, und Dozzle war am einfachsten einzurichten und zu benutzen. Dozzle ist ein Echtzeit-Log-Viewer für Docker-Container, siehe Links unten. Um es auszuführen, ziehen Sie einfach den Container, und Sie sind bereit zu gehen. Dozzle sieht aus wie Logs Explorer, eine Erweiterung für Docker Desktop, siehe Links unten.
Seien Sie vorsichtig, standardmäßig verbindet Dozzle Sie mit Google Analytics, stellen Sie sicher, dass Sie Dozzle mit dem '--no-analytics' Flag starten:
docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest --no-analytics
Sobald er gestartet ist, rufen Sie Ihren Browser auf:
http://127.0.0.1:8888
Mit Dozzle ist es sehr einfach, Ihre Docker -Protokolle einzusehen.
Die Suche verwendet regex, d.h. wenn Sie z.B. nach zwei Begriffen filtern wollen, 'Term1' und 'Term2',
können Sie in das Suchfeld eingeben:
Term1.*?Term2
Eine weitere nette Sache mit Dozzle ist, dass wir es auch für einen einzelnen Container oder ein paar Container starten können. Was wirklich fehlt, ist eine Möglichkeit, die Suchen zu speichern und wieder auszuwählen. Aber ich will mich nicht beschweren. Tolles Tool!
Zusammenfassung
Dies hat viel mehr Zeit in Anspruch genommen, als ich erwartet hatte. Es war viel mehr als nur das Drucken nach stdout. Ist es die Mühe wert? Für die aktuelle Anwendung, die aus vielen Diensten besteht, die durch Container repräsentiert werden, glaube ich, dass sie es ist.
Da die Protokollierung nun über Docker erfolgt, kann ich andere Möglichkeiten zur Anzeige der Protokolle, zur Erzeugung von Warnmeldungen usw. prüfen.
Links / Impressum
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
Mehr erfahren
Docker Docker-compose Log file Logging
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas