2016-05-03 16 views
2

Ich habe mehrere Module, die das gleiche utils.py-Paket verwenden. Wie kann ich den Logger in utils.py unterscheiden, ohne die Logger-Variable von den Aufrufern (d. H. ClassA oder ClassB) übergeben zu müssen?Wie können Sie verschiedene Logger verwenden, ohne die Logger-Variable zu übergeben? (Python-Protokollierung)

Sehr einfache Beispielcodes sind wie folgt. In Wirklichkeit habe ich viele Funktionen und Klassen in utils.py, und deshalb möchte ich die Variable logger nicht in utils.py übergehen.

~/test-two-loggers$ tree . 

├── main.py 
├── configs.py 
├── ClassA.py 
├── ClassB.py 
└── utils.py 

0 directories, 5 files 

main.py

import ClassA 
import ClassB 

ClassA.func() 
ClassB.func() 

ClassA.py

import utils 
import configs 
import logging 

def func(): 
    logger = logging.getLogger("classA") 
    logger.info("in ClassA") 
    utils.common_func(logger) # I want to change this line!!!! 

ClassB.py

import utils 
import configs 
import logging 

def func(): 
    logger = logging.getLogger("classB") 
    logger.info("in ClassB") 
    utils.common_func(logger) # I want to change this line!!!! 

utils.py

def common_func(logger): # I want to change this line!!!! 
    # do a lot of things ClassA and ClassB both need to do 
    logger.info("in utils - step one finished") 
    # do a lot of things ClassA and ClassB both need to do 
    logger.info("in utils - step two finished") 
    # do a lot of things ClassA and ClassB both need to do 
    logger.info("in utils - step three finished") 

configs.py

import logging.config 

logging_config = { 
     "version": 1, 
     "formatters": { 
      "formatter_a": { 
       "format": u"[A][%(levelname)s] %(module)s.%(lineno)d: %(message)s" 
      }, 
      "formatter_b": { 
       "format": u"[B][%(levelname)s] %(module)s.%(lineno)d: %(message)s" 
      }, 
     }, 
     "handlers": { 
      "console_a": { 
       "class": "logging.StreamHandler", 
       "level": "DEBUG", 
       "formatter": "formatter_a", 
       "stream": "ext://sys.stdout" 
      }, 
      "console_b": { 
       "class": "logging.StreamHandler", 
       "level": "DEBUG", 
       "formatter": "formatter_b", 
       "stream": "ext://sys.stdout" 
      }, 
     }, 
     "loggers": { 
      "classA": { 
       "level": "DEBUG", 
       "handlers": ["console_a"], 
       "propagate": "no" 
      }, 
      "classB": { 
       "level": "DEBUG", 
       "handlers": ["console_b"], 
       "propagate": "no" 
      }, 
     }, 
} 

logging.config.dictConfig(logging_config) 

Ergebnis Ich möchte:

~/test-two-loggers$ python main.py 
[A][INFO] ClassA.7: in ClassA 
[A][INFO] utils.3: in utils - step one finished 
[A][INFO] utils.5: in utils - step two finished 
[A][INFO] utils.7: in utils - step three finished 
[B][INFO] ClassB.7: in ClassB 
[B][INFO] utils.3: in utils - step one finished 
[B][INFO] utils.5: in utils - step two finished 
[B][INFO] utils.7: in utils - step three finished 

Aber ich will eine andere Lösung andere als dies. Ich möchte nicht die logger Variable in utils übergeben.

+1

Sie haben hier ein bisschen Code riechen. Wenn Sie ein Dienstprogramm-Modul haben, scheint es, als ob Sie von dem Dienstprogramm-Namespace protokollieren sollten. Wenn Sie einen Grund haben, den Stack-Trace zu verwenden, können Sie einfach Ihren eigenen Formatierer erstellen (https://github.com/python/cpython/blob/5b5fb38cc70122354c95f0a88cacaffab7d0b523/Lib/logging/__init__.py#L514). um herauszufinden, woher es hieß. –

+1

@WayneWerner 'Wenn Sie ein Dienstprogramm-Modul haben, scheint es, als ob Sie aus dem Dienstprogramm-Namespace protokollieren sollten. Aber das Log-In-Dienstprogramm wird ohne den Kontext weniger aussagekräftig sein (ob es von ClassA oder ClassB kommt, wenn ein Fehler im Dienstprogramm auftritt, welcher Aufrufer war und was zuvor in ClassA oder ClassB getan wurde, bevor das Dienstprogramm aufgerufen wurde). Aber ich denke, das ist auch eine Philosophie, die wir ausprobieren können. Vielleicht können wir die Anmeldung in einem Dienstprogramm unabhängig machen und Protokolle in Klasse A/B hinzufügen, um die Ergebniszusammenfassung im Dienstprogramm anzuzeigen. Das ist sicher eine Option. –

+0

Das ist definitiv meine Vorliebe - das Überarbeiten deiner App, um durch merkwürdige Holzrahmen zu springen, ist selten eine gute Idee. Sie können auch den Fn-Aufruf in ein try/except-Wrapping umbrechen und 'logger.exception ('problem with utility') 'verwenden und das traceback even mit einschließen. Das ist ein praktischer Weg, um außergewöhnlichen Fällen zu begegnen. –

Antwort

0

Sie können Ihren Formatierer ändern und das Schlüsselwort extra verwenden, um zusätzliche Wörterbuchargumente an Ihre Protokollierungsnachricht zu übergeben. Das erlaubt Ihnen, das "Modul" zu übergeben, von dem Sie behaupten wollen, dass Sie den Logger aufrufen.

So Ihre Formatter von ändern:

"[A][%(levelname)s] %(module)s.%(lineno)d: %(message)s" 

zu:

"[A][%(levelname)s] %(mymodule)s.%(lineno)d: %(message)s" 

Und rufen Sie Ihre Funktion als:

logger.info("in utils", extra={'mymodule':'somemodule'}) 

Falls Sie das realen Modul verwenden möchten Rufen Sie an, ändern Sie 'somemodule' zu __name__.

Ich auch vielleicht von nur überschreiben den Wert von module (so müssen Sie Ihre Formatierung ändern), aber die logging nicht erlaubt, so dass es scheint, Sie müssen Ihre Formatierung ändern.


EDIT:

Nur um es besonders deutlich zu machen, Ihre func() in ClassA.py geändert werden sollte:

def func(): 
    logger = logging.getLogger("classA") 
    logger.info("in ClassA", extra={'mymodule':__name__) 
    logger.info("in utils", extra={'mymodule':'utils') 
    utils.common_func() #call the function without passing the logger 

Und in Ihrem logging_config dict, sollten Sie die Zeichenfolge ändern module in logging_config['formatters']['formatter_a']['format'] zu mymodule.

Das gleiche sollte auf ClassB angewendet werden. Und offensichtlich sollten Sie die Zeile common_func entfernen, die logger verwendet.


Weitere Referenz:

+0

Hallo, ich verstehe deine Methode nicht ganz. Können Sie alle Codes einfügen, die geändert werden sollen? Und sind Sie sicher, dass dies die gleiche Ausgabe wie die alte generieren kann? –

+0

Ich denke, ich war klar genug, aber bearbeitet den Beitrag für mehr Klarheit. Übrigens brauchen Sie in diesem Fall sowieso nicht zwei Logger/Handler/Formatierer, denn der einzige Unterschied ist der Buchstabe A/B. Sie können Ihren Formatierer ändern und einen weiteren Schlüssel/Wert in "extra" hinzufügen, um A/B zu übergeben. [Wiederhole dich nicht] (https://en.wikipedia.org/wiki/Don't_repeat_yourself). –

+0

Hallo, ich verstehe Ihre Methode jetzt, aber es passt nicht meine Bedürfnisse. Was ich geschrieben habe, ist nur ein einfaches Beispiel. Die "Utils" und ihre Funktionen sollten nicht entfernt und in verschiedenen Modulen dupliziert werden. In Wirklichkeit gibt es viele Methoden in "utils.py" und deshalb teilen sich die beiden Klassen "utils". Entfernen Sie 'utils.py' und machen Sie Funktionen in' utils.py' zurück in 'ClassA' und' ClassB' würde zu vielen doppelten Codes in einem Projekt führen. –

0

Falls Sie müssen Sie verwenden, um den Logger innerhalb util.common_func Es gibt keine Notwendigkeit, es herumzugeben. Sie können einfach logging.getLogger verwenden, um Ihren Logger innerhalb utils.common_func abzurufen. Solange Sie die gleiche Zeichenfolge (z. B. classA) übergeben, gibt logging.getLogger das gleiche Protokollierungsobjekt zurück.

logger1 = logging.getLogger('classA') 
def func(): 
    logger2 = logging.getLogger('classA') 
    print logger1 is logger2 #True 

Vom documentation:

Beachten Sie, dass Loggers direkt nie instanziiert, sondern immer durch das Modul-Ebene Funktion logging.getLogger (name). Mehrere Aufrufe von getLogger() mit dem gleichen Namen werden immer einen Verweis auf dasselbe Objekt Logger zurückgeben.

+0

Hallo, aber es gibt sowohl 'classA' als auch' classB', die 'utils.common_func's Anrufer sind ...... Und somit ist das Problem. –

1

Sieht so aus, als ob Sie nach etwas wie impliziten Parametern suchen.

Dies ist etwas, das Python nicht hat (Explizit ist besser als implizit).

Aber wie immer gibt es eine mehr oder weniger elegante Weise, es zu emulieren:

class LoggerWrapper: 
    def __init__(self, logger_name): 
     self.logger = logging.getLogger(logger_name) 

    def common_func(self): 
     pass # do stuff here 

logger = LoggerWrapper('classA') 
logger.common_func() 
+0

Hallo, das ist die Methode, die ich denke! Anstatt "LoggerWrapper" ist es besser, es als "UtilsWithLogger" zu bezeichnen. Dies ist in der Tat eine Antwort, aber mit zwei unvollkommenen Punkten. (1) Es wird 'LoggerWrapper' init sowohl in ClassA als auch in ClassB benötigt (besser als die aktuelle Lösung, aber nicht perfekt) (2) Ich habe tatsächlich mehrere Klassen (Namensraumzweck) in' utils.py'. Wenn ich mit dieser Methode gehe, muss ich mich mit verschachtelten Klassen befassen, oder, wenn ich nicht mit verschachtelten Klassen umgehen will, muss ich alle Klassen in "utils" sowohl in "ClassA" als auch " KlasseB'. Ich werde warten, wenn es eine andere Methode gibt. –

+0

Ich verstehe Ihre zweite Ausgabe nicht ... Könnten Sie versuchen, es anders zu erklären? – GingerPlusPlus

+0

Hallo, etwa (2): In "utils.py" gibt es mehrere Klassen, z. 'Klasse CrawlerUtils - staticmethods crawl_json(), crawl_url()'/'Klasse DbUtils - staticmethods insert_with_md5(), my_insert_many()'/'Klasse OtherUtils ...'. Wenn ich also mit dieser Wrapper-Methode gehe, werde ich entweder (a) eine Elternklasse über diesen ursprünglichen Klassen hinzufügen, dann muss ich in verschachtelten Klassen mit 'self.logger' umgehen. oder (b) Machen 'crawler_utils = CrawlerUtilsWithLogger ('KlasseA'), db_utils = DbUtilsWithLoggers ('KlasseA'), another_utils = ... etc 'diese Initialisierungsschritte innerhalb von ClassA und ClassB. –

1

Die einfachste Lösung, vorausgesetzt, Sie nicht Ihre Anwendung Threading, ist nur das Modul Attribut monkeypatch:

util.py

import logging 

logger = logging.getLogger(__name__) 


def do_something(): 
    logger.info('Doing something') 

whatever.py

import logging 
from contextlib import contextmanager 


@contextmanager 
def patch_logger(module, logger): 
    ''' 
    Patch the given ``module``'s logger with 
    the provided ``logger``. Return the module's 
    original logger when we're done. 
    ''' 

    prev_logger = module.logger 
    try: 
     module.logger = logger 
     yield 
    finally: 
     module.logger = prev_logger 


class ClassA: 
    def __init__(self): 
     self.logger = logging.getLogger('A') 

    def func(self): 
     self.logger.info('Starting A') 
     with patch_logger(util, self.logger): 
      util.do_something() 


class ClassB: 
    def __init__(self): 
     self.logger = logging.getLogger('B') 

    def func(self): 
     self.logger.info('Starting B') 
     with patch_logger(util, self.logger): 
      util.do_something() 

Monkeypatching ist aus gutem Grund verpönt.

Verwandte Themen