So fügen Sie der Protokollierungsfunktion von Python ein benutzerdefiniertes Googlevel hinzu


116

Ich hätte gerne loglevel TRACE (5) für meine Anwendung, da ich das nicht für debug()ausreichend halte . Außerdem log(5, msg)ist es nicht das, was ich will. Wie kann ich einem Python-Logger ein benutzerdefiniertes Googlevel hinzufügen?

Ich habe eine mylogger.pymit folgendem Inhalt:

import logging

@property
def log(obj):
    myLogger = logging.getLogger(obj.__class__.__name__)
    return myLogger

In meinem Code verwende ich es folgendermaßen:

class ExampleClass(object):
    from mylogger import log

    def __init__(self):
        '''The constructor with the logger'''
        self.log.debug("Init runs")

Jetzt möchte ich anrufen self.log.trace("foo bar")

Vielen Dank im Voraus für Ihre Hilfe.

Bearbeiten (8. Dezember 2016): Ich habe die akzeptierte Antwort in pfa's geändert , was meiner Meinung nach eine ausgezeichnete Lösung ist, die auf dem sehr guten Vorschlag von Eric S. basiert.

Antworten:


171

@ Eric S.

Die Antwort von Eric S. ist ausgezeichnet, aber ich habe durch Experimente erfahren, dass dadurch immer Nachrichten gedruckt werden, die auf der neuen Debug-Ebene protokolliert wurden - unabhängig davon, auf welche Protokollstufe eingestellt ist. Wenn Sie also eine neue 9Ebenennummer festlegen setLevel(50), werden die Nachrichten der unteren Ebene fälschlicherweise gedruckt , wenn Sie anrufen .

Um dies zu verhindern, benötigen Sie eine weitere Zeile in der Funktion "debugv", um zu überprüfen, ob die betreffende Protokollierungsstufe tatsächlich aktiviert ist.

Beispiel behoben, das prüft, ob die Protokollierungsstufe aktiviert ist:

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    if self.isEnabledFor(DEBUG_LEVELV_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv

Wenn Sie sich den Code für class Loggerin logging.__init__.pyfür Python 2.7 ansehen, tun dies alle Standardprotokollfunktionen (.critical, .debug usw.).

Ich kann anscheinend keine Antworten auf die Antworten anderer veröffentlichen, weil ich keinen guten Ruf habe ... hoffentlich wird Eric seinen Beitrag aktualisieren, wenn er dies sieht. =)


7
Dies ist die bessere Antwort, da die Protokollstufe korrekt überprüft wird.
Colonel Panic

2
Sicher viel informativer als die aktuelle Antwort.
Mad Physicist

4
@pfa Was ist mit dem Hinzufügen, logging.DEBUG_LEVEL_NUM = 9damit Sie überall dort auf diese Debug-Ebene zugreifen können, wo Sie den Logger in Ihren Code importieren?
Edgarstack

4
Auf jeden Fall DEBUG_LEVEL_NUM = 9sollten Sie stattdessen definieren logging.DEBUG_LEVEL_NUM = 9. Auf diese Weise können Sie log_instance.setLevel(logging.DEBUG_LEVEL_NUM)die gleiche Art und Weise verwenden, wie Sie Right Know logging.DEBUGoderlogging.INFO
MaQ

Diese Antwort war sehr hilfreich. Danke pfa und EricS. Ich möchte der Vollständigkeit halber vorschlagen, zwei weitere Anweisungen aufzunehmen: logging.DEBUGV = DEBUG_LEVELV_NUMund logging.__all__ += ['DEBUGV'] Die zweite ist nicht besonders wichtig, aber die erste ist erforderlich, wenn Sie Code haben, der die Protokollierungsstufe dynamisch anpasst, und Sie möchten in der Lage sein, so etwas wie if verbose: logger.setLevel(logging.DEBUGV)`
Keith

63

Ich nahm die Antwort "Lambda vermeiden" und musste ändern, wo die log_at_my_log_level hinzugefügt wurde. Ich habe auch das Problem gesehen, das Paul gemacht hat. "Ich glaube nicht, dass das funktioniert. Benötigen Sie keinen Logger als erstes Argument in log_at_my_log_level?" Das hat bei mir funktioniert

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv

7
+1 auch. Ein eleganter Ansatz, der perfekt funktioniert hat. Ein wichtiger Hinweis: Sie müssen dies nur einmal in einem einzigen Modul tun, und es funktioniert für alle Module . Sie müssen nicht einmal das "Setup" -Modul importieren. Also werfen Sie dies in ein Paket __init__.pyund seien Sie glücklich: D
MestreLion

4
@ Eric S. Sie sollten einen Blick auf diese Antwort werfen
Sam Mussmann

1
Ich stimme @SamMussmann zu. Ich habe diese Antwort verpasst, weil dies die am besten gewählte Antwort war.
Colonel Panic

@ Eric S. Warum brauchst du Argumente ohne *? Wenn ich das mache, bekomme ich TypeError: not all arguments converted during string formattingaber es funktioniert gut mit *. (Python 3.4.3). Ist es ein Problem mit der Python-Version oder etwas, das mir fehlt?
Peter

Diese Antwort funktioniert bei mir nicht. Der Versuch, eine 'logging.debugv' zu machen, gibt einen FehlerAttributeError: module 'logging' has no attribute 'debugv'
Alex

51

Ich kombiniere alle vorhandenen Antworten mit einer Reihe von Nutzungserfahrungen und denke, ich habe eine Liste aller Dinge erstellt, die getan werden müssen, um eine vollständig nahtlose Nutzung der neuen Ebene sicherzustellen. Bei den folgenden Schritten wird davon ausgegangen, dass Sie eine neue Ebene TRACEmit Wert hinzufügen logging.DEBUG - 5 == 5:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') muss aufgerufen werden, um die neue Ebene intern zu registrieren, damit sie namentlich referenziert werden kann.
  2. Die neue Ebene muss aus loggingGründen der Konsistenz als Attribut hinzugefügt werden : logging.TRACE = logging.DEBUG - 5.
  3. Dem Modul tracemuss eine aufgerufene Methode hinzugefügt loggingwerden. Es sollte verhalten sich wie debug, infousw.
  4. Eine aufgerufene Methode tracemuss der aktuell konfigurierten Logger-Klasse hinzugefügt werden. Da dies nicht zu 100% garantiert ist logging.Logger, verwenden Sie logging.getLoggerClass()stattdessen.

Alle Schritte sind in der folgenden Methode dargestellt:

def addLoggingLevel(levelName, levelNum, methodName=None):
    """
    Comprehensively adds a new logging level to the `logging` module and the
    currently configured logging class.

    `levelName` becomes an attribute of the `logging` module with the value
    `levelNum`. `methodName` becomes a convenience method for both `logging`
    itself and the class returned by `logging.getLoggerClass()` (usually just
    `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
    used.

    To avoid accidental clobberings of existing attributes, this method will
    raise an `AttributeError` if the level name is already an attribute of the
    `logging` module or if the method name is already present 

    Example
    -------
    >>> addLoggingLevel('TRACE', logging.DEBUG - 5)
    >>> logging.getLogger(__name__).setLevel("TRACE")
    >>> logging.getLogger(__name__).trace('that worked')
    >>> logging.trace('so did this')
    >>> logging.TRACE
    5

    """
    if not methodName:
        methodName = levelName.lower()

    if hasattr(logging, levelName):
       raise AttributeError('{} already defined in logging module'.format(levelName))
    if hasattr(logging, methodName):
       raise AttributeError('{} already defined in logging module'.format(methodName))
    if hasattr(logging.getLoggerClass(), methodName):
       raise AttributeError('{} already defined in logger class'.format(methodName))

    # This method was inspired by the answers to Stack Overflow post
    # http://stackoverflow.com/q/2183233/2988730, especially
    # http://stackoverflow.com/a/13638084/2988730
    def logForLevel(self, message, *args, **kwargs):
        if self.isEnabledFor(levelNum):
            self._log(levelNum, message, args, **kwargs)
    def logToRoot(message, *args, **kwargs):
        logging.log(levelNum, message, *args, **kwargs)

    logging.addLevelName(levelNum, levelName)
    setattr(logging, levelName, levelNum)
    setattr(logging.getLoggerClass(), methodName, logForLevel)
    setattr(logging, methodName, logToRoot)

Sortieren Sie die Antworten nach Oldestund Sie werden es zu schätzen wissen, dass dies die beste Antwort von allen ist!
Serge Stroobandt

Vielen Dank. Ich habe ziemlich viel Arbeit geleistet, um so etwas zusammenzubasteln, und diese Qualitätssicherung war sehr hilfreich, deshalb habe ich versucht, etwas hinzuzufügen.
Mad Physicist

1
@ PeterDolan. Lassen Sie mich wissen, wenn Sie Probleme damit haben. In meiner persönlichen Toolbox befindet sich eine erweiterte Version, mit der Sie konfigurieren können, wie mit widersprüchlichen Ebenendefinitionen umgegangen wird. Das ist mir einmal eingefallen, weil ich gerne ein TRACE-Level hinzufüge, ebenso wie eine der Komponenten der Sphinx.
Verrückter Physiker

1
Ist das Fehlen eines Sternchens vor argsder logForLevelImplementierung beabsichtigt / erforderlich?
Chris L. Barnes

1
@ Tunesien. Es ist unbeabsichtigt. Danke für den Fang.
Mad Physicist

40

Diese Frage ist ziemlich alt, aber ich habe mich gerade mit dem gleichen Thema befasst und einen Weg gefunden, der den bereits erwähnten ähnlich ist und der mir etwas sauberer erscheint. Dies wurde auf 3.4 getestet, daher bin ich mir nicht sicher, ob die verwendeten Methoden in älteren Versionen vorhanden sind:

from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET

VERBOSE = 5

class MyLogger(getLoggerClass()):
    def __init__(self, name, level=NOTSET):
        super().__init__(name, level)

        addLevelName(VERBOSE, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        if self.isEnabledFor(VERBOSE):
            self._log(VERBOSE, msg, args, **kwargs)

setLoggerClass(MyLogger)

1
Dies ist meiner Meinung nach die beste Antwort, da dadurch das Patchen von Affen vermieden wird. Was getund setLoggerClassgenau tun und warum werden sie benötigt?
Marco Sulla

3
@MarcoSulla Sie sind als Teil des Python-Protokollierungsmoduls dokumentiert. Ich gehe davon aus, dass die dynamische Unterklassifizierung für den Fall verwendet wird, dass jemand während der Verwendung dieser Bibliothek einen eigenen Logger haben möchte. Dieser MyLogger würde dann eine Unterklasse meiner Klasse werden und die beiden kombinieren.
CrackerJack9

Dies ist der in dieser Diskussion vorgestellten Lösung sehr ähnlich, ob TRACEder Standardprotokollierungsbibliothek eine Ebene hinzugefügt werden soll . +1
IMP1

18

Wer hat mit der schlechten Praxis begonnen, interne Methoden anzuwenden ( self._log) und warum basiert jede Antwort darauf?! Die pythonische Lösung wäre, self.logstattdessen zu verwenden, damit Sie sich nicht mit internen Dingen herumschlagen müssen:

import logging

SUBDEBUG = 5
logging.addLevelName(SUBDEBUG, 'SUBDEBUG')

def subdebug(self, message, *args, **kws):
    self.log(SUBDEBUG, message, *args, **kws) 
logging.Logger.subdebug = subdebug

logging.basicConfig()
l = logging.getLogger()
l.setLevel(SUBDEBUG)
l.subdebug('test')
l.setLevel(logging.DEBUG)
l.subdebug('test')

18
Die Verwendung von _log () anstelle von log () ist erforderlich, um die Einführung einer zusätzlichen Ebene im Aufrufstapel zu vermeiden. Wenn log () verwendet wird, führt die Einführung des zusätzlichen Stapelrahmens dazu, dass mehrere LogRecord-Attribute (funcName, lineno, filename, pathname, ...) auf die Debug-Funktion anstatt auf den tatsächlichen Aufrufer verweisen. Dies ist wahrscheinlich nicht das gewünschte Ergebnis.
Rivy

5
Seit wann ist das Aufrufen der internen Methoden einer Klasse nicht zulässig? Nur weil die Funktion außerhalb der Klasse definiert ist, bedeutet dies nicht, dass es sich um eine externe Methode handelt.
OozeMeister

3
Diese Methode ändert nicht nur die Stapelverfolgung unnötig, sondern überprüft auch nicht, ob die richtige Ebene protokolliert wird.
Mad Physicist

Ich denke, was @schlamar sagt, ist richtig, aber der Gegengrund hat die gleiche Anzahl von Stimmen erhalten. Also, was zu verwenden?
Sumit Murari

1
Warum würde eine Methode keine interne Methode verwenden?
Gringo Suave

9

Ich finde es einfacher, ein neues Attribut für das Logger-Objekt zu erstellen, das die Funktion log () übergibt. Ich denke, das Logger-Modul liefert aus genau diesem Grund den addLevelName () und den log (). Somit sind keine Unterklassen oder neue Methoden erforderlich.

import logging

@property
def log(obj):
    logging.addLevelName(5, 'TRACE')
    myLogger = logging.getLogger(obj.__class__.__name__)
    setattr(myLogger, 'trace', lambda *args: myLogger.log(5, *args))
    return myLogger

jetzt

mylogger.trace('This is a trace message')

sollte wie erwartet funktionieren.


Hätte dies nicht einen kleinen Leistungseinbruch gegenüber Unterklassen? Bei diesem Ansatz müssen einige jedes Mal, wenn sie nach einem Logger fragen, den setattr-Aufruf ausführen. Sie würden diese wahrscheinlich in einer benutzerdefinierten Klasse zusammenfassen, aber dennoch muss setattr für jeden erstellten Logger aufgerufen werden, oder?
Matthew Lund

@Zbigniew unten zeigte an, dass dies nicht funktionierte, was meiner Meinung nach daran liegt, dass Ihr Logger seinen Anruf tätigen muss _log, nicht log.
Marqueed

9

Obwohl wir bereits viele richtige Antworten haben, ist das Folgende meiner Meinung nach pythonischer:

import logging

from functools import partial, partialmethod

logging.TRACE = 5
logging.addLevelName(logging.TRACE, 'TRACE')
logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE)
logging.trace = partial(logging.log, logging.TRACE)

Wenn Sie mypyIhren Code verwenden möchten , wird empfohlen, ihn hinzuzufügen # type: ignore, um Warnungen beim Hinzufügen von Attributen zu unterdrücken.


1
Es sieht gut aus, aber die letzte Zeile ist verwirrend. Sollte es nicht sein logging.trace = partial(logging.log, logging.TRACE) # type: ignore?
Sergey Nudnov

@ SergeyNudnov danke für den Hinweis, ich habe es behoben. War ein Fehler von meiner Seite, ich habe nur meinen Code kopiert und anscheinend die Reinigung durcheinander gebracht.
DerWeh

8

Ich denke, Sie müssen die LoggerKlasse unterordnen und eine Methode hinzufügen, die aufgerufen wird traceund im Grunde Logger.logmit einer niedrigeren Stufe als aufruft DEBUG. Ich habe dies nicht versucht, aber dies ist, was die Dokumente anzeigen .


3
Und Sie möchten wahrscheinlich ersetzen logging.getLogger, um Ihre Unterklasse anstelle der integrierten Klasse zurückzugeben.
S.Lott

4
@ S.Lott - Eigentlich (zumindest mit der aktuellen Version von Python, vielleicht war es 2010 nicht der Fall) müssen Sie verwenden setLoggerClass(MyClass)und dann getLogger()wie gewohnt anrufen ...
Mac

IMO, dies ist bei weitem die beste (und pythonischste) Antwort, und wenn ich ihr mehrere +1 hätte geben können, hätte ich es getan. Es ist einfach auszuführen, aber Beispielcode wäre nett gewesen. :-D
Doug R.

@ DougR.Danke, aber wie gesagt, ich habe es nicht versucht. :)
Noufal Ibrahim

6

Tipps zum Erstellen eines benutzerdefinierten Loggers:

  1. Nicht verwenden _log, verwenden log(Sie müssen nicht überprüfen isEnabledFor)
  2. Das Protokollierungsmodul sollte diejenige sein, die eine Instanz des benutzerdefinierten Protokollierers erstellt, da es etwas Magie bewirkt. Daher getLoggermüssen Sie die Klasse über festlegensetLoggerClass
  3. Sie müssen __init__für den Logger keine Klasse definieren , wenn Sie nichts speichern
# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
    def trace(self, msg, *args, **kwargs):
        self.log(TRACE, msg, *args, **kwargs)

Wenn Sie diesen Logger aufrufen, verwenden Sie ihn setLoggerClass(MyLogger), um ihn zum Standardlogger zu machengetLogger

logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")

Sie werden müssen setFormatter, setHandlerund setLevel(TRACE)auf die handlerund auf die logsich tatsächlich auf diese se geringe Spur


3

Das hat bei mir funktioniert:

import logging
logging.basicConfig(
    format='  %(levelname)-8.8s %(funcName)s: %(message)s',
)
logging.NOTE = 32  # positive yet important
logging.addLevelName(logging.NOTE, 'NOTE')      # new level
logging.addLevelName(logging.CRITICAL, 'FATAL') # rename existing

log = logging.getLogger(__name__)
log.note = lambda msg, *args: log._log(logging.NOTE, msg, args)
log.note('school\'s out for summer! %s', 'dude')
log.fatal('file not found.')

Das Lambda / FuncName-Problem wurde mit logger._log behoben, wie @marqueed hervorhob. Ich denke, die Verwendung von Lambda sieht ein bisschen sauberer aus, aber der Nachteil ist, dass es keine Schlüsselwortargumente annehmen kann. Ich habe das selbst nie benutzt, also kein Problem.

  HINWEIS Einrichtung: Die Schule ist für den Sommer aus! Kumpel
  FATAL Setup: Datei nicht gefunden.

2

Nach meiner Erfahrung ist dies die vollständige Lösung des Problems der Operation. Um zu vermeiden, dass "Lambda" als die Funktion angesehen wird, in der die Nachricht gesendet wird, gehen Sie tiefer:

MY_LEVEL_NUM = 25
logging.addLevelName(MY_LEVEL_NUM, "MY_LEVEL_NAME")
def log_at_my_log_level(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(MY_LEVEL_NUM, message, args, **kws)
logger.log_at_my_log_level = log_at_my_log_level

Ich habe noch nie versucht, mit einer eigenständigen Logger-Klasse zu arbeiten, aber ich denke, die Grundidee ist dieselbe (benutze _log).


Ich denke nicht, dass das funktioniert. Benötigen Sie nicht loggerals erstes Argument log_at_my_log_level?
Paul

Ja, ich denke du würdest es wahrscheinlich tun. Diese Antwort wurde aus Code übernommen, der ein etwas anderes Problem löst.
Marked

2

Ergänzung zum Beispiel von Mad Physicists, um den Dateinamen und die Zeilennummer korrekt zu erhalten:

def logToRoot(message, *args, **kwargs):
    if logging.root.isEnabledFor(levelNum):
        logging.root._log(levelNum, message, args, **kwargs)

1

Basierend auf der angehefteten Antwort habe ich eine kleine Methode geschrieben, mit der automatisch neue Protokollierungsstufen erstellt werden

def set_custom_logging_levels(config={}):
    """
        Assign custom levels for logging
            config: is a dict, like
            {
                'EVENT_NAME': EVENT_LEVEL_NUM,
            }
        EVENT_LEVEL_NUM can't be like already has logging module
        logging.DEBUG       = 10
        logging.INFO        = 20
        logging.WARNING     = 30
        logging.ERROR       = 40
        logging.CRITICAL    = 50
    """
    assert isinstance(config, dict), "Configuration must be a dict"

    def get_level_func(level_name, level_num):
        def _blank(self, message, *args, **kws):
            if self.isEnabledFor(level_num):
                # Yes, logger takes its '*args' as 'args'.
                self._log(level_num, message, args, **kws) 
        _blank.__name__ = level_name.lower()
        return _blank

    for level_name, level_num in config.items():
        logging.addLevelName(level_num, level_name.upper())
        setattr(logging.Logger, level_name.lower(), get_level_func(level_name, level_num))

Konfiguration kann so aussehen:

new_log_levels = {
    # level_num is in logging.INFO section, that's why it 21, 22, etc..
    "FOO":      21,
    "BAR":      22,
}

0

Als Alternative zum Hinzufügen einer zusätzlichen Methode zur Logger-Klasse würde ich die Verwendung der Logger.log(level, msg)Methode empfehlen .

import logging

TRACE = 5
logging.addLevelName(TRACE, 'TRACE')
FORMAT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'


logging.basicConfig(format=FORMAT)
l = logging.getLogger()
l.setLevel(TRACE)
l.log(TRACE, 'trace message')
l.setLevel(logging.DEBUG)
l.log(TRACE, 'disabled trace message')

0

Ich bin verwirrt; Zumindest mit Python 3.5 funktioniert es einfach:

import logging


TRACE = 5
"""more detail than debug"""

logging.basicConfig()
logging.addLevelName(TRACE,"TRACE")
logger = logging.getLogger('')
logger.debug("n")
logger.setLevel(logging.DEBUG)
logger.debug("y1")
logger.log(TRACE,"n")
logger.setLevel(TRACE)
logger.log(TRACE,"y2")
    

Ausgabe:

DEBUG: root: y1

TRACE: root: y2


1
Dies lässt Sie nicht tun, logger.trace('hi')was meiner Meinung nach das Hauptziel ist
Ultimation

-3

Für den Fall, dass jemand dem Protokollierungsmodul (oder einer Kopie davon) automatisch eine neue Protokollierungsstufe hinzufügen möchte, habe ich diese Funktion erstellt und die Antwort von @ pfa erweitert:

def add_level(log_name,custom_log_module=None,log_num=None,
                log_call=None,
                   lower_than=None, higher_than=None, same_as=None,
              verbose=True):
    '''
    Function to dynamically add a new log level to a given custom logging module.
    <custom_log_module>: the logging module. If not provided, then a copy of
        <logging> module is used
    <log_name>: the logging level name
    <log_num>: the logging level num. If not provided, then function checks
        <lower_than>,<higher_than> and <same_as>, at the order mentioned.
        One of those three parameters must hold a string of an already existent
        logging level name.
    In case a level is overwritten and <verbose> is True, then a message in WARNING
        level of the custom logging module is established.
    '''
    if custom_log_module is None:
        import imp
        custom_log_module = imp.load_module('custom_log_module',
                                            *imp.find_module('logging'))
    log_name = log_name.upper()
    def cust_log(par, message, *args, **kws):
        # Yes, logger takes its '*args' as 'args'.
        if par.isEnabledFor(log_num):
            par._log(log_num, message, args, **kws)
    available_level_nums = [key for key in custom_log_module._levelNames
                            if isinstance(key,int)]

    available_levels = {key:custom_log_module._levelNames[key]
                             for key in custom_log_module._levelNames
                            if isinstance(key,str)}
    if log_num is None:
        try:
            if lower_than is not None:
                log_num = available_levels[lower_than]-1
            elif higher_than is not None:
                log_num = available_levels[higher_than]+1
            elif same_as is not None:
                log_num = available_levels[higher_than]
            else:
                raise Exception('Infomation about the '+
                                'log_num should be provided')
        except KeyError:
            raise Exception('Non existent logging level name')
    if log_num in available_level_nums and verbose:
        custom_log_module.warn('Changing ' +
                                  custom_log_module._levelNames[log_num] +
                                  ' to '+log_name)
    custom_log_module.addLevelName(log_num, log_name)

    if log_call is None:
        log_call = log_name.lower()

    setattr(custom_log_module.Logger, log_call, cust_log)
    return custom_log_module

1
Eval inside exec. Beeindruckend.
Mad Physicist

2
..... weiß nicht, was mich dazu gebracht hat .... nach so vielen Monaten würde ich diese Aussage gerne mit einer setattrstattdessen austauschen ...
Vasilis Lemonidis
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.