Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 391 – Dictionary-basierte Konfiguration für Logging

Autor:
Vinay Sajip <vinay_sajip at red-dove.com>
Status:
Final
Typ:
Standards Track
Erstellt:
15-Okt-2009
Python-Version:
2.7, 3.2
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Dieses PEP beschreibt eine neue Methode zur Konfiguration von Logging, bei der ein Dictionary zur Speicherung von Konfigurationsinformationen verwendet wird.

Begründung

Die derzeitigen Methoden zur Konfiguration des Python Logging-Pakets bestehen entweder darin, die Logging-API zur programmatischen Konfiguration zu verwenden oder Konfigurationsdateien auf Basis von ConfigParser zu nutzen.

Programmatische Konfiguration bietet zwar maximale Kontrolle, bindet die Konfiguration jedoch an Python-Code. Dies erleichtert die Laufzeitänderung nicht und führt dazu, dass die Möglichkeit, die Ausführlichkeit des Loggings für verschiedene Teile einer Anwendung flexibel zu erhöhen und zu verringern, verloren geht. Dies schränkt die Nutzbarkeit des Loggings als Hilfsmittel zur Diagnose von Problemen ein – und manchmal ist Logging das einzige verfügbare Diagnosemittel in Produktionsumgebungen.

Das ConfigParser-basierte Konfigurationssystem ist nutzbar, erlaubt seinen Benutzern jedoch nicht, alle Aspekte des Logging-Pakets zu konfigurieren. Zum Beispiel können Filter nicht mit diesem System konfiguriert werden. Darüber hinaus scheint das ConfigParser-Format in einigen Kreisen Ablehnung (manchmal starke Ablehnung) hervorzurufen. Obwohl es gewählt wurde, weil es das einzige Konfigurationsformat war, das zu dieser Zeit in den Python-Standards unterstützt wurde, betrachten viele Leute es (oder vielleicht nur das spezifische Schema, das für die Logging-Konfiguration gewählt wurde) als „schrottig“ oder „hässlich“, in einigen Fällen scheinbar aus rein ästhetischen Gründen.

Neuere Versionen von Python enthalten JSON-Unterstützung in der Standardbibliothek, und dies kann auch als Konfigurationsformat verwendet werden. In anderen Umgebungen, wie z.B. Google App Engine, wird YAML zur Konfiguration von Anwendungen verwendet, und üblicherweise würde die Konfiguration des Loggings als integraler Bestandteil der Anwendungs-Konfiguration betrachtet. Obwohl die Standardbibliothek derzeit keine YAML-Unterstützung enthält, kann die Unterstützung für JSON und YAML auf gemeinsame Weise bereitgestellt werden, da beide Serialisierungsformate eine Deserialisierung in Python-Dictionaries ermöglichen.

Durch die Bereitstellung einer Möglichkeit, Logging durch Übergabe der Konfiguration in einem Dictionary zu konfigurieren, wird das Logging einfacher zu konfigurieren sein, nicht nur für Benutzer von JSON und/oder YAML, sondern auch für Benutzer von benutzerdefinierten Konfigurationsmethoden, indem ein gemeinsames Format zur Beschreibung der gewünschten Konfiguration bereitgestellt wird.

Ein weiterer Nachteil des aktuellen ConfigParser-basierten Konfigurationssystems ist, dass es keine inkrementelle Konfiguration unterstützt: Eine neue Konfiguration ersetzt die bestehende Konfiguration vollständig. Obwohl eine vollständige Flexibilität für inkrementelle Konfiguration in einer Multithreading-Umgebung schwierig bereitzustellen ist, ermöglicht der neue Konfigurationsmechanismus die Bereitstellung eingeschränkter Unterstützung für inkrementelle Konfiguration.

Spezifikation

Die Spezifikation besteht aus zwei Teilen: der API und dem Format des Dictionaries, das zur Übermittlung von Konfigurationsinformationen verwendet wird (d.h. dem Schema, dem es entsprechen muss).

Benennung

Historisch gesehen war das Logging-Paket nicht PEP 8-konform. Zu einem zukünftigen Zeitpunkt wird dies durch Änderung von Methoden- und Funktionsnamen im Paket zur Einhaltung von PEP 8 korrigiert. Im Interesse der Einheitlichkeit verwenden die vorgeschlagenen Ergänzungen zur API jedoch ein Namensschema, das mit dem aktuellen Schema von Logging übereinstimmt.

API

Das Modul logging.config erhält folgende Ergänzung:

  • Eine Funktion namens dictConfig(), die ein einzelnes Argument entgegennimmt – das Dictionary, das die Konfiguration enthält. Bei Fehlern während der Verarbeitung des Dictionaries werden Ausnahmen ausgelöst.

Es wird möglich sein, diese API anzupassen – siehe Abschnitt API-Anpassung. Inkrementelle Konfiguration wird in einem eigenen Abschnitt behandelt.

Dictionary Schema - Überblick

Bevor das Schema im Detail beschrieben wird, lohnt es sich, ein paar Worte über Objektverbindungen, Unterstützung für benutzerdefinierte Objekte und Zugriff auf externe und interne Objekte zu sagen.

Objektverbindungen

Das Schema soll eine Menge von Logging-Objekten – Logger, Handler, Formatter, Filter – beschreiben, die in einem Objektgraphen miteinander verbunden sind. Daher muss das Schema Verbindungen zwischen den Objekten darstellen. Angenommen, ein bestimmter Logger hat nach der Konfiguration einen bestimmten Handler angehängt. Für die Zwecke dieser Diskussion können wir sagen, dass der Logger die Quelle und der Handler das Ziel einer Verbindung zwischen beiden darstellt. Natürlich wird dies in den konfigurierten Objekten durch die Tatsache repräsentiert, dass der Logger eine Referenz auf den Handler hält. Im Konfigurations-Dictionary geschieht dies, indem jedem Zielobjekt eine ID gegeben wird, die es eindeutig identifiziert, und dann die ID im Konfigurations-Objekt der Quelle verwendet wird, um anzuzeigen, dass eine Verbindung zwischen der Quelle und dem Zielobjekt mit dieser ID besteht.

Betrachten wir zum Beispiel den folgenden YAML-Snippet:

formatters:
  brief:
    # configuration for formatter with id 'brief' goes here
  precise:
    # configuration for formatter with id 'precise' goes here
handlers:
  h1: #This is an id
   # configuration of handler with id 'h1' goes here
   formatter: brief
  h2: #This is another id
   # configuration of handler with id 'h2' goes here
   formatter: precise
loggers:
  foo.bar.baz:
    # other configuration for logger 'foo.bar.baz'
    handlers: [h1, h2]

(Hinweis: YAML wird in diesem Dokument verwendet, da es etwas lesbarer ist als die entsprechende Python-Quellform für das Dictionary.)

Die IDs für Logger sind die Loggernamen, die programmatisch verwendet würden, um eine Referenz auf diese Logger zu erhalten, z.B. foo.bar.baz. Die IDs für Formatter und Filter können beliebige Zeichenwerte sein (wie brief, precise oben) und sind flüchtig, d.h. sie sind nur für die Verarbeitung des Konfigurations-Dictionaries aussagekräftig und werden zur Bestimmung von Verbindungen zwischen Objekten verwendet und nicht irgendwo gespeichert, wenn der Konfigurationsaufruf abgeschlossen ist.

Handler-IDs werden speziell behandelt, siehe Abschnitt Handler-IDs unten.

Der obige Snippet gibt an, dass dem Logger namens foo.bar.baz zwei Handler angehängt werden sollen, die durch die Handler-IDs h1 und h2 beschrieben werden. Der Formatter für h1 ist der durch die ID brief beschriebene und der Formatter für h2 ist der durch die ID precise beschriebene.

Benutzerdefinierte Objekte

Das Schema soll benutzerdefinierte Objekte für Handler, Filter und Formatter unterstützen. (Logger benötigen keine unterschiedlichen Typen für verschiedene Instanzen, daher gibt es keine Unterstützung – in der Konfiguration – für benutzerdefinierte Logger-Klassen.)

Zu konfigurierende Objekte werden typischerweise durch Dictionaries beschrieben, die ihre Konfiguration detailliert darstellen. An einigen Stellen kann das Logging-System aus dem Kontext ableiten, wie ein Objekt instanziiert werden soll, aber wenn ein benutzerdefiniertes Objekt instanziiert werden soll, weiß das System nicht, wie dies geschehen soll. Um vollständige Flexibilität für die Instanziierung benutzerdefinierter Objekte zu bieten, muss der Benutzer eine „Factory“ bereitstellen – ein aufrufbares Objekt, das mit einem Konfigurations-Dictionary aufgerufen wird und das instanziierte Objekt zurückgibt. Dies wird durch einen absoluten Importpfad zur Factory signalisiert, der unter dem speziellen Schlüssel '()' verfügbar gemacht wird. Hier ein konkretes Beispiel:

formatters:
  brief:
    format: '%(message)s'
  default:
    format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'
  custom:
      (): my.package.customFormatterFactory
      bar: baz
      spam: 99.9
      answer: 42

Der obige YAML-Snippet definiert drei Formatter. Der erste, mit der ID brief, ist eine Standard-Instanz von logging.Formatter mit dem angegebenen Formatstring. Der zweite, mit der ID default, hat ein längeres Format und definiert auch das Zeitformat explizit und ergibt eine logging.Formatter-Instanz, die mit diesen beiden Formatstrings initialisiert wird. In Python-Quellform dargestellt, haben die Formatter brief und default Konfigurations-Unter-Dictionaries

{
  'format' : '%(message)s'
}

und

{
  'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
  'datefmt' : '%Y-%m-%d %H:%M:%S'
}

jeweils, und da diese Dictionaries nicht den speziellen Schlüssel '()' enthalten, wird die Instanziierung aus dem Kontext abgeleitet: als Ergebnis werden Standard-Instanzen von logging.Formatter erstellt. Das Konfigurations-Unter-Dictionary für den dritten Formatter, mit der ID custom, ist

{
  '()' : 'my.package.customFormatterFactory',
  'bar' : 'baz',
  'spam' : 99.9,
  'answer' : 42
}

und dieses enthält den speziellen Schlüssel '()', was bedeutet, dass eine benutzerdefinierte Instanziierung gewünscht ist. In diesem Fall wird die angegebene Factory-Funktion verwendet. Wenn es sich um eine tatsächliche aufrufbare Funktion handelt, wird sie direkt verwendet – andernfalls, wenn Sie einen String (wie im Beispiel) angeben, wird die tatsächliche aufrufbare Funktion über normale Importmechanismen gefunden. Die aufrufbare Funktion wird mit den *restlichen* Elementen im Konfigurations-Unter-Dictionary als Schlüsselwortargumente aufgerufen. Im obigen Beispiel wird angenommen, dass der Formatter mit der ID custom durch den Aufruf zurückgegeben wird

my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)

Der Schlüssel '()' wurde als spezieller Schlüssel verwendet, da er kein gültiger Name für ein Schlüsselwortargument ist und daher nicht mit den Namen der in dem Aufruf verwendeten Schlüsselwortargumente kollidiert. Der '()' dient auch als Merkhilfe, dass der entsprechende Wert eine aufrufbare Funktion ist.

Zugriff auf externe Objekte

Es gibt Fälle, in denen eine Konfiguration auf Objekte außerhalb der Konfiguration verweisen muss, z.B. sys.stderr. Wenn das Konfigurations-Dictionary mit Python-Code erstellt wird, ist dies unkompliziert, aber ein Problem tritt auf, wenn die Konfiguration über eine Textdatei (z.B. JSON, YAML) bereitgestellt wird. In einer Textdatei gibt es keine standardmäßige Möglichkeit, sys.stderr vom literalen String 'sys.stderr' zu unterscheiden. Um diese Unterscheidung zu erleichtern, sucht das Konfigurationssystem nach bestimmten Sonderpräfixen in String-Werten und behandelt diese speziell. Wenn beispielsweise der literale String 'ext://sys.stderr' als Wert in der Konfiguration angegeben wird, wird ext:// entfernt und der Rest des Wertes wird über normale Importmechanismen verarbeitet.

Die Behandlung solcher Präfixe erfolgt analog zur Protokollbehandlung: Es wird einen generischen Mechanismus geben, um nach Präfixen zu suchen, die dem regulären Ausdruck ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$ entsprechen, wobei, wenn das prefix erkannt wird, der suffix auf eine präfixabhängige Weise verarbeitet wird und das Ergebnis der Verarbeitung den String-Wert ersetzt. Wenn das Präfix nicht erkannt wird, bleibt der String-Wert unverändert.

Die Implementierung wird eine Reihe von Standardpräfixen wie ext:// bereitstellen, aber es wird möglich sein, den Mechanismus vollständig zu deaktivieren oder zusätzliche oder andere Präfixe für eine spezielle Behandlung bereitzustellen.

Zugriff auf interne Objekte

Neben externen Objekten gibt es manchmal auch die Notwendigkeit, auf Objekte in der Konfiguration zu verweisen. Dies wird vom Konfigurationssystem implizit für Dinge vorgenommen, die es kennt. Zum Beispiel wird der String-Wert 'DEBUG' für eine level in einem Logger oder Handler automatisch in den Wert logging.DEBUG konvertiert, und die Einträge handlers, filters und formatter nehmen eine Objekt-ID an und werden zum entsprechenden Zielobjekt aufgelöst.

Es muss jedoch ein generischerer Mechanismus für den Fall benutzerdefinierter Objekte bereitgestellt werden, die Logging nicht kennt. Nehmen Sie zum Beispiel die Instanz von logging.handlers.MemoryHandler, die einen target als einen anderen Handler, an den delegiert werden soll, entgegennimmt. Da das System diese Klasse bereits kennt, muss in der Konfiguration der angegebene target nur die Objekt-ID des entsprechenden Zielhandlers sein, und das System wird den Handler anhand der ID auflösen. Wenn jedoch ein Benutzer my.package.MyHandler definiert, der einen alternate Handler hat, wüsste das Konfigurationssystem nicht, dass der alternate auf einen Handler verweist. Um dem Rechnung zu tragen, wird ein generisches Auflösungssystem bereitgestellt, das es dem Benutzer ermöglicht, anzugeben

handlers:
  file:
    # configuration of file handler goes here

  custom:
    (): my.package.MyHandler
    alternate: cfg://handlers.file

Der literale String 'cfg://handlers.file' wird analog zu den Strings mit dem ext://-Präfix aufgelöst, jedoch durch Suche im Konfigurations-Dictionary selbst anstatt im Import-Namensraum. Der Mechanismus ermöglicht den Zugriff über Punkte oder Indizes, ähnlich wie bei str.format. Daher würde bei folgendem Snippet

handlers:
  email:
    class: logging.handlers.SMTPHandler
    mailhost: localhost
    fromaddr: my_app@domain.tld
    toaddrs:
      - support_team@domain.tld
      - dev_team@domain.tld
    subject: Houston, we have a problem.

in der Konfiguration der String 'cfg://handlers' zu dem Dictionary mit dem Schlüssel handlers aufgelöst, der String 'cfg://handlers.email zu dem Dictionary mit dem Schlüssel email im Dictionary handlers aufgelöst, und so weiter. Der String 'cfg://handlers.email.toaddrs[1] würde zu 'dev_team.domain.tld' aufgelöst und der String 'cfg://handlers.email.toaddrs[0]' zum Wert 'support_team@domain.tld' aufgelöst. Der subject Wert könnte entweder über 'cfg://handlers.email.subject' oder äquivalent über 'cfg://handlers.email[subject]' abgerufen werden. Die letztere Form muss nur verwendet werden, wenn der Schlüssel Leerzeichen oder nicht-alphanumerische Zeichen enthält. Wenn ein Indexwert nur dezimale Ziffern enthält, wird versucht, über den entsprechenden Ganzzahlwert zuzugreifen, wobei bei Bedarf auf den String-Wert zurückgegriffen wird.

Bei einem String cfg://handlers.myhandler.mykey.123 wird dieser zu config_dict['handlers']['myhandler']['mykey']['123'] aufgelöst. Wenn der String als cfg://handlers.myhandler.mykey[123] angegeben wird, versucht das System, den Wert von config_dict['handlers']['myhandler']['mykey'][123] abzurufen, und greift auf config_dict['handlers']['myhandler']['mykey']['123'] zurück, wenn dies fehlschlägt.

Handler-IDs

Einige spezifische Logging-Konfigurationen erfordern die Verwendung von Handler-Levels, um den gewünschten Effekt zu erzielen. Im Gegensatz zu Loggern, die immer anhand ihrer Namen identifiziert werden können, haben Handler keine persistenten Handles, über die Level über einen inkrementellen Konfigurationsaufruf geändert werden können.

Daher schlägt dieses PEP die Hinzufügung einer optionalen name-Eigenschaft zu Handlern vor. Wenn diese verwendet wird, wird ein Eintrag in einem Dictionary hinzugefügt, das den Namen dem Handler zuordnet. (Der Eintrag wird entfernt, wenn der Handler geschlossen wird.) Wenn ein inkrementeller Konfigurationsaufruf erfolgt, werden Handler in diesem Dictionary gesucht, um das Level des Handlers entsprechend dem Wert in der Konfiguration festzulegen. Siehe Abschnitt Inkrementelle Konfiguration für weitere Details.

Theoretisch könnte eine solche „persistente Namens“-Funktion auch für Filter und Formatter bereitgestellt werden. Es gibt jedoch keinen zwingenden Grund, diese inkrementell konfigurieren zu können. Nach dem Prinzip „Praktikabilität schlägt Reinheit“ erhalten nur Handler diese neue name-Eigenschaft. Die ID eines Handlers in der Konfiguration wird sein name.

Das Handler-Namens-Lookup-Dictionary dient nur Konfigurationszwecken und wird nicht Teil der öffentlichen API des Pakets.

Dictionary Schema - Detail

Das an dictConfig() übergebene Dictionary muss die folgenden Schlüssel enthalten:

  • version – auf einen Ganzzahlwert zu setzen, der die Schemaversion darstellt. Der einzig gültige Wert ist derzeit 1, aber dieser Schlüssel ermöglicht die Weiterentwicklung des Schemas unter Beibehaltung der Abwärtskompatibilität.

Alle anderen Schlüssel sind optional, werden aber, wenn vorhanden, wie unten beschrieben interpretiert. In allen Fällen, in denen ein „konfigurierendes Dictionary“ erwähnt wird, wird es auf den speziellen Schlüssel '()' geprüft, um festzustellen, ob eine benutzerdefinierte Instanziierung erforderlich ist. Wenn ja, wird der beschriebene Mechanismus verwendet, um zu instanziieren; andernfalls wird der Kontext zur Bestimmung der Instanziierung verwendet.

  • formatters – der entsprechende Wert ist ein Dictionary, in dem jeder Schlüssel eine Formatter-ID und jeder Wert ein Dictionary ist, das beschreibt, wie die entsprechende Formatter-Instanz konfiguriert wird.

    Das konfigurierende Dictionary wird auf die Schlüssel format und datefmt (mit Standardwerten None) durchsucht und diese werden verwendet, um eine logging.Formatter-Instanz zu erstellen.

  • filters – der entsprechende Wert ist ein Dictionary, in dem jeder Schlüssel eine Filter-ID und jeder Wert ein Dictionary ist, das beschreibt, wie die entsprechende Filter-Instanz konfiguriert wird.

    Das konfigurierende Dictionary wird auf den Schlüssel name (Standardwert ist ein leerer String) durchsucht und dieser wird verwendet, um eine logging.Filter-Instanz zu erstellen.

  • handlers – der entsprechende Wert ist ein Dictionary, in dem jeder Schlüssel eine Handler-ID und jeder Wert ein Dictionary ist, das beschreibt, wie die entsprechende Handler-Instanz konfiguriert wird.

    Das konfigurierende Dictionary wird auf die folgenden Schlüssel durchsucht:

    • class (obligatorisch). Dies ist der vollständig qualifizierte Name der Handler-Klasse.
    • level (optional). Das Level des Handlers.
    • formatter (optional). Die ID des Formatters für diesen Handler.
    • filters (optional). Eine Liste von IDs der Filter für diesen Handler.

    Alle *anderen* Schlüssel werden als Schlüsselwortargumente an den Konstruktor des Handlers übergeben. Zum Beispiel bei folgendem Snippet:

    handlers:
      console:
        class : logging.StreamHandler
        formatter: brief
        level   : INFO
        filters: [allow_foo]
        stream  : ext://sys.stdout
      file:
        class : logging.handlers.RotatingFileHandler
        formatter: precise
        filename: logconfig.log
        maxBytes: 1024
        backupCount: 3
    

    wird der Handler mit der ID console als logging.StreamHandler instanziiert, wobei sys.stdout als zugrundeliegender Stream verwendet wird. Der Handler mit der ID file wird als logging.handlers.RotatingFileHandler mit den Schlüsselwortargumenten filename='logconfig.log', maxBytes=1024, backupCount=3 instanziiert.

  • loggers – der entsprechende Wert ist ein Dictionary, in dem jeder Schlüssel ein Loggernamen und jeder Wert ein Dictionary ist, das beschreibt, wie die entsprechende Logger-Instanz konfiguriert wird.

    Das konfigurierende Dictionary wird auf die folgenden Schlüssel durchsucht:

    • level (optional). Das Level des Loggers.
    • propagate (optional). Die Propagation-Einstellung des Loggers.
    • filters (optional). Eine Liste von IDs der Filter für diesen Logger.
    • handlers (optional). Eine Liste von IDs der Handler für diesen Logger.

    Die angegebenen Logger werden entsprechend dem angegebenen Level, der Propagation, den Filtern und den Handlern konfiguriert.

  • root – dies ist die Konfiguration für den Root-Logger. Die Verarbeitung der Konfiguration erfolgt wie bei jedem Logger, außer dass die propagate-Einstellung nicht anwendbar ist.
  • incremental – ob die Konfiguration als inkrementell zur bestehenden Konfiguration interpretiert werden soll. Dieser Wert hat den Standardwert False, was bedeutet, dass die angegebene Konfiguration die bestehende Konfiguration mit denselben Semantiken ersetzt, wie sie von der bestehenden fileConfig() API verwendet werden.

    Wenn der angegebene Wert True ist, wird die Konfiguration wie im Abschnitt Inkrementelle Konfiguration unten beschrieben verarbeitet.

  • disable_existing_loggers – ob vorhandene Logger deaktiviert werden sollen. Diese Einstellung spiegelt den gleichnamigen Parameter in fileConfig() wider. Wenn nicht vorhanden, ist dieser Parameter standardmäßig auf True gesetzt. Dieser Wert wird ignoriert, wenn incremental auf True gesetzt ist.

Ein funktionierendes Beispiel

Das Folgende ist eine tatsächliche funktionierende Konfiguration im YAML-Format (außer dass die E-Mail-Adressen ungültig sind):

formatters:
  brief:
    format: '%(levelname)-8s: %(name)-15s: %(message)s'
  precise:
    format: '%(asctime)s %(name)-15s %(levelname)-8s %(message)s'
filters:
  allow_foo:
    name: foo
handlers:
  console:
    class : logging.StreamHandler
    formatter: brief
    level   : INFO
    stream  : ext://sys.stdout
    filters: [allow_foo]
  file:
    class : logging.handlers.RotatingFileHandler
    formatter: precise
    filename: logconfig.log
    maxBytes: 1024
    backupCount: 3
  debugfile:
    class : logging.FileHandler
    formatter: precise
    filename: logconfig-detail.log
    mode: a
  email:
    class: logging.handlers.SMTPHandler
    mailhost: localhost
    fromaddr: my_app@domain.tld
    toaddrs:
      - support_team@domain.tld
      - dev_team@domain.tld
    subject: Houston, we have a problem.
loggers:
  foo:
    level : ERROR
    handlers: [debugfile]
  spam:
    level : CRITICAL
    handlers: [debugfile]
    propagate: no
  bar.baz:
    level: WARNING
root:
  level     : DEBUG
  handlers  : [console, file]

Inkrementelle Konfiguration

Es ist schwierig, vollständige Flexibilität für inkrementelle Konfigurationen zu bieten. Da Objekte wie Filter und Formatter anonym sind, ist es beispielsweise nach der Einrichtung einer Konfiguration nicht möglich, auf solche anonymen Objekte zu verweisen, wenn eine Konfiguration erweitert wird.

Darüber hinaus gibt es keinen zwingenden Grund, den Objektgraphen von Loggern, Handlern, Filtern und Formatter zur Laufzeit willkürlich zu ändern, sobald eine Konfiguration eingerichtet ist; die Ausführlichkeit von Loggern und Handlern kann einfach durch Setzen von Levels (und im Fall von Loggern, Propagation-Flags) gesteuert werden. Die willkürliche Änderung des Objektgraphen auf sichere Weise ist in einer Multithreading-Umgebung problematisch; zwar nicht unmöglich, aber die Vorteile rechtfertigen nicht die Komplexität, die sie der Implementierung hinzufügt.

Wenn der Schlüssel incremental eines Konfigurations-Dictionaries vorhanden und True ist, ignoriert das System daher alle Einträge für formatters und filters vollständig und verarbeitet nur die level-Einstellungen in den handlers-Einträgen sowie die level- und propagate-Einstellungen in den loggers- und root-Einträgen.

Es ist sicherlich möglich, inkrementelle Konfigurationen auf andere Weise bereitzustellen, z.B. indem dictConfig() ein Schlüsselwortargument incremental annimmt, das standardmäßig False ist. Der Grund für die vorgeschlagene Verwendung eines Wertes im Konfigurations-Dictionary ist, dass dies die Übertragung von Konfigurationen über das Netzwerk als gepickelte Dictionaries an einen Socket-Listener ermöglicht. Somit kann die Logging-Ausführlichkeit einer langlaufenden Anwendung im Laufe der Zeit geändert werden, ohne dass die Anwendung gestoppt und neu gestartet werden muss.

Hinweis: Feedback zu inkrementellen Konfigurationsanforderungen auf Basis Ihrer praktischen Erfahrungen wird besonders begrüßt.

API-Anpassung

Die grundlegende dictConfig() API ist für alle Anwendungsfälle nicht ausreichend. Es wird eine Möglichkeit zur Anpassung der API durch Bereitstellung des Folgenden geschaffen:

  • Eine Klasse namens DictConfigurator, deren Konstruktor das für die Konfiguration verwendete Dictionary übergeben bekommt und die eine configure()-Methode besitzt.
  • Eine aufrufbare Funktion namens dictConfigClass, die (standardmäßig) auf DictConfigurator gesetzt wird. Dies dient dazu, dass DictConfigurator bei Bedarf durch eine geeignete benutzerdefinierte Implementierung ersetzt werden kann.

Die Funktion dictConfig() ruft dictConfigClass auf, übergibt das angegebene Dictionary und ruft dann die configure()-Methode des zurückgegebenen Objekts auf, um die Konfiguration tatsächlich wirksam zu machen.

def dictConfig(config):
    dictConfigClass(config).configure()

Dies sollte alle Anpassungsanforderungen erfüllen. Zum Beispiel könnte eine Unterklasse von DictConfigurator in ihrer eigenen __init__() DictConfigurator.__init__() aufrufen, dann benutzerdefinierte Präfixe einrichten, die in dem nachfolgenden configure() call verwendet werden können. dictConfigClass würde an die Unterklasse gebunden werden, und dann könnte dictConfig() genau wie im standardmäßigen, unmodifizierten Zustand aufgerufen werden.

Änderung an der Socket-Listener-Implementierung

Die bestehende Socket-Listener-Implementierung wird wie folgt geändert: Wenn eine Konfigurationsnachricht empfangen wird, wird versucht, diese mit dem json-Modul zu einem Dictionary zu deserialisieren. Wenn dieser Schritt fehlschlägt, wird angenommen, dass die Nachricht im fileConfig-Format vorliegt und wie bisher verarbeitet. Wenn die Deserialisierung erfolgreich ist, wird dictConfig() aufgerufen, um das resultierende Dictionary zu verarbeiten.

Konfigurationsfehler

Wenn während der Konfiguration ein Fehler auftritt, löst das System eine ValueError, TypeError, AttributeError oder ImportError mit einer entsprechend aussagekräftigen Nachricht aus. Die folgende Liste (möglicherweise unvollständig) enthält Bedingungen, die einen Fehler auslösen werden:

  • Ein level, das kein String ist oder ein String ist, der keinem tatsächlichen Logging-Level entspricht
  • Ein propagate-Wert, der keine Boolesche ist
  • Eine ID, die kein entsprechendes Ziel hat
  • Eine nicht existierende Handler-ID, die bei einem inkrementellen Aufruf gefunden wird
  • Ein ungültiger Loggernamen
  • Unfähigkeit, auf ein internes oder externes Objekt zuzugreifen

Diskussion in der Community

Das PEP wurde auf python-dev und python-list angekündigt. Obwohl es keine riesige Menge an Diskussion gab, ist dies vielleicht für ein Nischenthema zu erwarten.

Diskussionsfäden auf python-dev

https://mail.python.org/pipermail/python-dev/2009-October/092695.html https://mail.python.org/pipermail/python-dev/2009-October/092782.html https://mail.python.org/pipermail/python-dev/2009-October/093062.html

Und auf python-list

https://mail.python.org/pipermail/python-list/2009-October/1223658.html https://mail.python.org/pipermail/python-list/2009-October/1224228.html

Es gab einige zustimmende Kommentare zum Vorschlag, keine Einwände gegen den Vorschlag als Ganzes und einige Fragen und Einwände zu spezifischen Details. Diese wurden vom Autor nach eigenen Angaben durch Änderungen am PEP adressiert.

Referenzimplementierung

Eine Referenzimplementierung der Änderungen ist als Modul dictconfig.py mit begleitenden Unit-Tests in test_dictconfig.py verfügbar unter:

http://bitbucket.org/vinay.sajip/dictconfig

Dies beinhaltet alle Funktionen außer der Änderung am Socket-Listener.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0391.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT