In Python unterscheidet man
- DEBUG
- INFO
- WARNING
- ERROR
- CRITICAL
Ich hätte debug und info (und evtl. auch warning) eher auf stdout erwartet.
Ich wollte zuerst einen neuen Thread aufmachen, aber dachte mir, dass passt hier doch ganz gut.Meillo hat geschrieben:16.10.2022 22:42:12Bei Filter-Programmen muessen alle Meldungen auf Stderr rauskommen, da sie sonst ja in mitten des normalen Programmoutputs in der Pipe bzw. Ausgabedatei verschwinden. Dann wuerde z.B. ein `wc' am Ende falsch zaehlen.
Stdout ist fuer die normale Programmausgabe gedacht. Stderr ist fuer Meldungen, die beim menschlichen Bediener ankommen sollen.
OK, dann habe ich jetzt mal ein konkretes Beispiel. Hyperorg ist eine CLI Anwendung, die Org-Dateien (ein markup-Format aus dem Emacs Umfeld) aus einem Verzeichnis liest, in HTML-Dateien konvertiert und in ein neues Verzeichnis schreibt.Meillo hat geschrieben:06.12.2022 09:57:22Ich denke, es waere einfacher, anhand von konkreten Beispielen zu ueberlegen. Kannst du vielleicht mal die Ausgaben deines Programms posten und wir sagen dir, wie wir diese klassifizieren wuerden?
Code: Alles auswählen
$ hyperorg --version
hyperorg 0.0.0a1
$ hyperorg
usage: hyperorg [-h] [-v] [-d] [--version] inputdir outputdir
hyperorg: error: the following arguments are required: inputdir, outputdir
Code: Alles auswählen
$ hyperorg my.org-roam my.html-roam
Code: Alles auswählen
$ hyperorg my.org-roam my.html-roam -v
INFO : Using my.org-roam as input directory.
INFO : Done. 414 nodes loaded.
INFO : Using my.html-roam as output directory.
INFO : Delete all files and folders in the output directory.
INFO : Done. See you next time.
Code: Alles auswählen
$ hyperorg my.org-roam my.html-roam -d
[2023-01-03 12:14:41,331] [INFO] reader.py:__init__() => Using my.org-roam as input directory.
[2023-01-03 12:14:41,577] [INFO] reader.py:read_org_files() => Done. 414 nodes loaded.
[2023-01-03 12:14:41,578] [INFO] exporter.py:__init__() => Using my.html-roam as output directory.
[2023-01-03 12:14:41,578] [DEBUG] exporter.py:_set_css_content() => Re-use CSS-file "my.html-roam/style.css".
[2023-01-03 12:14:41,578] [INFO] exporter.py:_clean_output_dir() => Delete all files and folders in the output directory.
[2023-01-03 12:14:41,593] [DEBUG] exporter.py:export_to_html() => Node: Identification of Seniors at Risk (ISAR) Screening
[2023-01-03 12:14:41,597] [DEBUG] exporter.py:export_to_html() => Node: Raumstrukturierung bei Sekundärdaten-Analysen
[2023-01-03 12:14:41,597] [DEBUG] exporter.py:export_to_html() => Node: Venkatesh; Morris; Davis; Davis (2003) User Acceptance of Information Technology
[2023-01-03 12:14:41,598] [DEBUG] exporter.py:export_to_html() => Node: EUROSTAT Nutzung
[2023-01-03 12:14:41,598] [DEBUG] exporter.py:export_to_html() => Node: Python import structure for package testing from itself and its prject
[...SNIPPED...]
[2023-01-03 12:14:41,822] [DEBUG] exporter.py:export_to_html() => Node: Qualitätsindikatoren
[2023-01-03 12:14:41,822] [DEBUG] exporter.py:export_to_html() => Node: ProcessPoolExecutor
[2023-01-03 12:14:41,824] [INFO] __main__.py:main() => Done. See you next time.
Code: Alles auswählen
hyperorg -d my.org-roam my.html-roam >hyperorg.debug && echo success
So verstehe ich das eigentlich auch.Meillo hat geschrieben:03.01.2023 14:06:11Ich wuerde alle vom User angeforderten Ausgaben nach Stdout schreiben, also die Versionsmeldung und die Verbose- und Debug-Ausgaben.
Das passt fuer mich eigentlich schon wie es ist. Logging ist das was einheitlich ins Logging-Backend geht, sei es direkt in eine Textdatei oder zu Syslog oder ins Terminal oder sonstwo hin. Dort waere es falsch, Teile davon woanders hin zu schicken.buhtz hat geschrieben:03.01.2023 16:21:14Aber das logging Modul von Python schreibt wirklich alles nach stderr, auch INFO und DEBUG. Als kleiner Nixblicker denke ich mir immer, dass sich das die Python-Hasen schon gut überlegt haben.
Nach deiner Regel wäre es ja einfach, alle logging-Meldung von WARNING und "schlimmer" (also ERROR und CRITICAL) nach stderr und INFO und DEBUG nach stdout. Ist aber nicht so voreingstellt bei Python. Umbiegen kann man das dort natürlich, aber vermutlich ist es so nicht gedacht.
So sollte es auch sein. Im einen Fall hat man den Befehl ja richtig aufgerufen, um die Usage-Meldung ausgeben zu lassen. Im anderen Fall hat man den Befehl falsch aufgerufen, naemlich ohne Argumente, obwohl er welche benoetigt.buhtz hat geschrieben:03.01.2023 16:21:14EDIT: Beim usage von argparse ist es differenziert. Ruf man hyperorg ohne Argumente auf, ist das eigentlich ein Fehler, woraufhin argparse eine verkürzte Version des usage ausgibt; tatsächlich auf stderr. Fordert man explizit die usage infos an per -h werden diese auf stdout ausgeben. Aha.
Danke fuer den Hinweis. Die Meinungen dort decken sich mit meinen Aussagen.buhtz hat geschrieben:04.01.2023 11:04:51Hier die Diskussion auf der Python-Liste: https://mail.python.org/pipermail/pytho ... 08709.html
Da dürftest du recht haben.Meillo hat geschrieben:04.01.2023 12:06:00...
Ich habe den Eindruck, dass du das Konzept von Logging nicht so verstanden hast, wie es die meisten verstehen.
...
nochmal an der Idee von Logging an sich ansetzen, wozu man es nutzt, was es von Programmausgaben unterscheidet.
Code: Alles auswählen
debugprint(3, "foo bar");
Na dann!buhtz hat geschrieben:04.01.2023 14:24:38Mhm... Eigentlich ist es in den Python-Docs ganz gut beschrieben, in welchen Situationen man welche Mechanismen (logging, print, warning) nutzen sollte/könnte.
https://docs.python.org/howto/logging.h ... se-logging
Code: Alles auswählen
print("Error: foo fails", file=sys.stderr)
logging.error("foo fails")
Von stderr nach stdout verschieben lassen sich die Meldungen ganz leicht:buhtz hat geschrieben:03.01.2023 16:21:14Umbiegen kann man das dort natürlich, aber vermutlich ist es so nicht gedacht.
Code: Alles auswählen
import logging
import sys
# Just log to stdout instead of stderr:
logging.basicConfig(stream=sys.stdout)
Mit einem geeigneten logging-Setup muss man das nicht überall im Code von Hand duplizieren. Man kann mehrere Handler benutzen:Meillo hat geschrieben:04.01.2023 14:45:20Bei einem Programmfehler muesstest du dann beides machen:
Code: Alles auswählen
import logging
import logging.handlers
import sys
# Log everything to syslog (or a file, …) and errors additionally to stderr:
logger = logging.getLogger()
# No fancy formatting for the syslog messages. syslog has better ways to
# e.g. signal the level:
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
syslog_handler.setLevel(logging.NOTSET)
stderr_handler = logging.StreamHandler(sys.stderr)
stderr_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
stderr_handler.setLevel(logging.ERROR)
# Not using basicConfig() here, we want to setup different formatters for
# each handler.
logger.setLevel(logging.NOTSET)
logger.addHandler(syslog_handler)
logger.addHandler(stderr_handler)
# Could add even more log targets here: file(s) (FileHandler), mail
# (SMTPHandler), etc.; each with their own custom level, formatting, and
# more.
Das ist auch nicht nötig. Dafür gibt es ja Loglevel, mit denen man auch ein --verbose oder --debug umsetzen kann:buhtz hat geschrieben:04.01.2023 14:06:09Ich kann ja nicht vor fast jede print() Zeile ein if verbose: setzen.
Code: Alles auswählen
import argparse
import logging
import sys
# Use levels to handle --debug, --verbose, and --quiet options:
argparser = argparse.ArgumentParser()
argparser.add_argument(
"--debug", action="store_const", const=logging.DEBUG, dest="log_level"
)
argparser.add_argument(
"--quiet", action="store_const", const=logging.ERROR, dest="log_level"
)
argparser.add_argument(
"--verbose", action="store_const", const=logging.INFO, dest="log_level"
)
argparser.set_defaults(log_level=logging.WARNING)
args = argparser.parse_args()
# Don’t use an "INFO: " prefix for info messages.
info_handler = logging.StreamHandler()
info_handler.addFilter(lambda r: r.levelno == logging.INFO)
info_handler.setFormatter(logging.Formatter())
other_handler = logging.StreamHandler()
other_handler.addFilter(lambda r: r.levelno != logging.INFO)
other_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logging.basicConfig(level=args.log_level, handlers=[info_handler, other_handler])
Code: Alles auswählen
import socket
# Just use the logging functions later in the code. No further checks for the
# configured verbosity necessary.
try:
raise Exception("This was a mistake :-(")
except Exception as e:
logging.exception("There was an exceptional exception!")
logging.critical("BOOM!")
logging.error("Zonk …")
logging.warning("Close call")
logging.info("Hi, log reading user!")
# Using printf formatting so messages don’t get formatted when they would not
# be printed anyway because of the configured level.
logging.debug('Your hostname is "%s"', socket.gethostname())
print(f"\nThe effective level is {logging.getLogger().getEffectiveLevel()}")