2009-06-10 4 views
11

Tak więc mamy teraz wiele skryptów Pythona i staramy się je skonsolidować i naprawić i zwolnień. Jedną z rzeczy, które próbujemy wykonać, jest upewnienie się, że wszystkie sys.stdout/sys.stderr trafiają do modułu logowania Pythona.Przekierowywanie sys.stdout do logowania Python

Teraz najważniejsze jest, chcemy wydrukować następuje:

[<ERROR LEVEL>] | <TIME> | <WHERE> | <MSG> 

Teraz wszystkie sys.stdout/wiad sys.stderr prawie we wszystkich komunikatów o błędach python są w formacie [poziom ] - MSG, które są napisane przy użyciu sys.stdout/sys.stderr. Mogę parsować grzywnę w opakowaniu sys.stdout i w opakowaniu sys.stderr. Następnie wywołaj odpowiedni poziom rejestrowania, w zależności od analizowanego wejścia.

W zasadzie mamy pakiet o nazwie foo i podpakiet o nazwie log. W __init__.py nam określić, co następuje:

def initLogging(default_level = logging.INFO, stdout_wrapper = None, \ 
       stderr_wrapper = None): 
    """ 
     Initialize the default logging sub system 
    """ 
    root_logger = logging.getLogger('') 
    strm_out = logging.StreamHandler(sys.__stdout__) 
    strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_TIME_FORMAT, \ 
              DEFAULT_LOG_TIME_FORMAT)) 
    root_logger.setLevel(default_level) 
    root_logger.addHandler(strm_out) 

    console_logger = logging.getLogger(LOGGER_CONSOLE) 
    strm_out = logging.StreamHandler(sys.__stdout__) 
    #strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_MSG_FORMAT, \ 
    #          DEFAULT_LOG_TIME_FORMAT)) 
    console_logger.setLevel(logging.INFO) 
    console_logger.addHandler(strm_out) 

    if stdout_wrapper: 
     sys.stdout = stdout_wrapper 
    if stderr_wrapper: 
     sys.stderr = stderr_wrapper 


def cleanMsg(msg, is_stderr = False): 
    logy = logging.getLogger('MSG') 
    msg = msg.rstrip('\n').lstrip('\n') 
    p_level = r'^(\s+)?\[(?P<LEVEL>\w+)\](\s+)?(?P<MSG>.*)$' 
    m = re.match(p_level, msg) 
    if m: 
     msg = m.group('MSG') 
     if m.group('LEVEL') in ('WARNING'): 
      logy.warning(msg) 
      return 
     elif m.group('LEVEL') in ('ERROR'): 
      logy.error(msg) 
      return 
    if is_stderr: 
     logy.error(msg) 
    else: 
     logy.info(msg) 

class StdOutWrapper: 
    """ 
     Call wrapper for stdout 
    """ 
    def write(self, s): 
     cleanMsg(s, False) 

class StdErrWrapper: 
    """ 
     Call wrapper for stderr 
    """ 
    def write(self, s): 
     cleanMsg(s, True) 

teraz nazwalibyśmy to w jednym z naszych scenariuszy na przykład:

import foo.log 
foo.log.initLogging(20, foo.log.StdOutWrapper(), foo.log.StdErrWrapper()) 
sys.stdout.write('[ERROR] Foobar blew') 

który byłby przekształcony wiadomości dziennika błędów. Jak:

[ERROR] | 20090610 083215 | __init__.py | Foobar Blew 

Teraz problem jest, gdy to zrobimy, moduł gdzie komunikat o błędzie był zalogowany jest obecnie __init__ (odpowiadające foo.log.__init__.py pliku), który pokonuje cały cel.

Próbowałem zrobić deepCopy/shallowCopy obiektów stderr/stdout, ale to nic nie robi, to nadal mówi moduł, którego wiadomość pojawiła się w __init__.py. Jak mogę to zrobić, aby tak się nie stało?

Odpowiedz

6

Problem polega na tym, że moduł rejestrowania szuka jednej warstwy w górę stosu wywołań, aby znaleźć osobę, która go nazwała, ale teraz twoja funkcja jest w tym momencie warstwą pośrednią (chociaż spodziewam się, że raport będzie zawierał cleanMsg, a nie __init__, ponieważ tam właśnie dzwonisz do log()). Zamiast tego musisz przejść do dwóch poziomów lub przekazać komuś, kto jest dzwoniącym, do zalogowanej wiadomości. Możesz to zrobić, samodzielnie sprawdzając ramkę stosu i chwytając funkcję wywołującą, wstawiając ją do wiadomości.

Aby znaleźć ramkę telefonicznej, można użyć modułu wglądu:

import inspect 
f = inspect.currentframe(N) 

będzie patrzeć N ramek, i powrót wskaźnika ramki. tzn. Twój bezpośredni rozmówca to currentframe (1), ale możesz potrzebować przejść kolejną klatkę, jeśli jest to metoda stdout.write. Po utworzeniu ramki wywołania można uzyskać obiekt kodu wykonawczego i sprawdzić skojarzony z nim plik i nazwę funkcji.np:

code = f.f_code 
caller = '%s:%s' % (code.co_filename, code.co_name) 

Być może trzeba umieścić jakiś kod do obsługi Kod telefoniczny do was non-python (czyli funkcje C lub poleceń wbudowanych.), gdyż mogą one brakuje obiektów f_code.

Alternatywnie, kontynuując mikej's answer, możesz użyć tego samego podejścia w niestandardowej klasie Logger dziedziczącej z rejestrowania. Logger, który przesłania findCaller, aby nawigować po kilku klatkach, zamiast jednego.

2

Myślę, że problemem jest to, że rzeczywiste wiadomości dziennika są teraz tworzone przez logy.error i logy.info połączeń w cleanMsg, stąd ta metoda jest źródłem wiadomości dziennika i widzisz to jako __init__.py

Jeśli spójrz na źródło Pythona lib/logging/__init__.py zobaczysz metodę o nazwie findCaller, która jest tym, czego moduł rejestrowania używa do wyprowadzenia wywołującego żądania rejestrowania.
Być może można to zmienić w obiekcie rejestrowania, aby dostosować zachowanie?