Python Günlüğü ile iki kez görünen günlük mesajları


101

Python günlük kaydı kullanıyorum ve bazı nedenlerden dolayı, tüm mesajlarım iki kez görünüyor.

Günlük kaydını yapılandırmak için bir modülüm var:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Daha sonra, günlük kaydını yapılandırmak için bu yöntemi çağırıyorum:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

Ve sonra buy_ham modülü içinde şunu arayacağım:

self.logger.info('Successfully able to write to %s' % path)

Ve bazı nedenlerden dolayı, tüm mesajlar iki kez görünüyor. Akış işleyicilerinden birini yorumladım, hala aynı şey. Biraz tuhaf, bunun neden olduğundan emin değilim ... lol. Bariz bir şeyi kaçırdığımı varsayarsak.

Şerefe, Victor


1
configure_logging()İki kez çağrılmadığından emin misiniz (örneğin kurucu tarafından da)? Boy () öğesinin yalnızca bir örneği mi oluşturuldu?
Jacek Konieczny

1
self.logger.handlers = [ch]Bunun yerine kullanmak bu sorunu çözecektir, ancak en iyisi bu kodu iki kez çalıştırmadığınızdan emin olmaktır, örneğin, if not self.loggerbaşlangıçta kullanarak .
Ninjakannon

Yanıtlar:


135

configure_loggingİki kez çağırıyorsunuz (belki __init__yönteminde Boy): getLoggeraynı nesneyi döndürür, ancak addHandlergünlükçüye benzer bir işleyicinin önceden eklenip eklenmediğini kontrol etmez.

Bu yönteme yönelik çağrıları izlemeyi ve bunlardan birini ortadan kaldırmayı deneyin. Ya bir bayrak kurmak logging_initializediçin başlatılır Falseiçinde __init__yöntemine Boyve değişim configure_loggingeğer hiçbir şey yapmamak logging_initializedolduğunu Trueve bunu ayarlamak için Truesize logger başlatıldı sonra mümkün olur.

Programınız birkaç Boyörnek oluşturuyorsa configure_logging, işleyicileri ekleyen global bir işlevle işleri yapma şeklinizi ve Boy.configure_loggingyalnızca self.loggerniteliği başlatan yöntemi değiştirmeniz gerekir .

Bunu çözmenin başka bir yolu da kaydedicinizin handlers özniteliğini kontrol etmektir:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
Evet, haklıydın - aptal ben. Bunu init'te ve açıkça başka yerlerde aradım . Lol. Teşekkürler =).
victorhooi

Teşekkürler. Çözümünüz bugün beni kurtardı.
ForeverLearner

1
Benim durumumda 6 kez görünüyorlardı. Bundan şüphelenmiştim çünkü 6 oop sınıfında aynı tip günlükçüyü ilan ettim
answerSeeker

5
Burada deneyimimi paylaşmak istiyorum: Geliştirdiğim bir Flask uygulaması için günlük mesajları İKİ KEZDEN FAZLA görünüyordu. Uygulama ve modüller yüklendiğinde, loggerkullanılan değişkenin sınıflarımdan birinden başlatılan değil, loggerPython3 önbelleğinde bulunan değişken olduğu için günlük dosyasında arttığını söyleyebilirim. ve işleyici, yapılandırdığım bir AppScheduler tarafından her 60 saniyede bir eklendi. Yani bu if not logger.handlers, bu tür olaylardan kaçınmanın oldukça akıllıca bir yolu. Çözüm için teşekkürler yoldaş :)!
ivanleoncz

2
Bu sorunu Flask uygulamamda görüyorum. Bu çözüm, ana şişe uygulamasında oluşturulan günlük mesajlarıyla ilgili sorunu çözdü, ancak uygulamam bir kitaplık modülünde işlevleri çalıştırıyor ve bu kitaplıktan gelen iletiler hala birden çok kez günlüğe kaydediliyor. Bunu nasıl düzelteceğimi bilmiyorum.
Cas

27

Bu sorunu görüyorsanız ve işleyiciyi iki kez eklemiyorsanız, abarnert'in cevabına buradan bakın.

Gönderen docs :

Not: Bir kaydediciye ve onun atalarından bir veya daha fazlasına bir işleyici iliştirirseniz, aynı kaydı birden çok kez yayınlayabilir. Genel olarak, birden fazla kaydediciye bir işleyici eklemenize gerek yoktur - eğer bunu sadece günlükçü hiyerarşisinde en yüksek olan uygun kaydediciye eklerseniz, yayılmaları koşuluyla tüm alt kaydediciler tarafından kaydedilen tüm olayları görür. ayarı True olarak bırakılır. Yaygın bir senaryo, işleyicileri yalnızca kök kaydediciye eklemek ve yayılmanın gerisini halletmesine izin vermektir.

Bu nedenle, "test" için özel bir işleyici istiyorsanız ve mesajlarının aynı zamanda kök işleyiciye gitmesini istemiyorsanız, yanıt basittir: propagatebayrağını kapatın :

logger.propagate = False

1
En iyi cevap bu. Posterin amacına uymadı (kodlamada mantıksal hata) ama çoğu zaman durum böyle olmalıdır.
Artem

Bravo. Bu, kopyaların gerçek nedenidir (çoğu genel durum için).
Bay Duhart

Kodumla ilgili sorun da buydu. Çok teşekkürler.
harshit

Şimdiye kadarki en iyi cevap. Teşekkür ederim!
Foivos Ts

Neden bu varsayılan olamaz? Tüm kaydedicilerimi "kök" altında oluşturuyorum. + dizin yapısı, böylece 'kök' kaydediciden her şeyi kolayca kontrol edebilirim
MortenB

8

İşleyici, dışarıdan her aradığınızda eklenir. İşinizi bitirdikten sonra İşleyiciyi Kaldırmayı deneyin:

self.logger.removeHandler(ch)

1
logger.handlers.pop() Python 2.7'de kullandım , hile yapıyor
radtek

7

Ben bir python acemisiyim, ancak bu benim için işe yaradı (Python 2.7)

while logger.handlers:
     logger.handlers.pop()

4

Benim durumumda, logger.propagate = Falseçift ​​yazdırmayı önlemek için ayarlamam gerekir.

Aşağıdaki kodda kaldırırsanız logger.propagate = Falseçift ​​baskı göreceksiniz.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

Sahip olduğum sorun bu. Teşekkür ederim
q0987

Mükemmel cevap; ekleyerek logger.propagate = Falsegünlüğü balona en etmek Garson tarafından barındırılan bir Matara uygulamasında, çift günlüğü önlemek için çözüm oldu app.loggermesela.
bluebinary

1

Yüklü kök işleyici yoksa logging.debug()aramalara yapılan çağrı logging.basicConfig(). Bu benim için, test vakalarının tetiklenme sırasını kontrol edemediğim bir test çerçevesinde gerçekleşiyordu. Başlatma kodum ikincisini kuruyordu. Varsayılan, istemediğim günlük kaydı.BASIC_FORMAT kullanıyor.


Sanırım benim için olan bu. Konsol kaydedicilerin otomatik olarak oluşturulmasını nasıl engellersiniz?
Robert

@Robert, ilk kayıt çağrısından önce istediğiniz kaydedici ile başlatıldığınızdan emin olmaktır. Çerçeveleri test etmek bunu belirsizleştirebilir, ancak bunu yapmanın bir yolu olmalı. Ayrıca, çoklu işlem yapıyorsanız, her işlem için aynısını yapmanız gerekir.
JimB

1

Kaydediciye bir şey çıktısını alırsanız (yanlışlıkla) sonra onu yapılandırırsanız, çok geç olacaktır. Örneğin, kodumda

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

Gibi bir şey alırdım (biçim seçeneklerini göz ardı ederek)

look out
hello
hello

ve her şey stdout'a iki kez yazılmıştır. Bunun nedeni, ilk çağrının logging.warningotomatik olarak yeni bir işleyici oluşturması ve ardından açıkça başka bir işleyici ekledim. Yanlışlıkla yapılan ilk logging.warningaramayı kaldırdığımda sorun ortadan kalktı.


0

Konsol günlüklerinin iki katına çıktığı ancak dosya günlüklerimin olmadığı garip bir durumla karşılaşıyordum. Bir ton kazdıktan sonra anladım.

Lütfen üçüncü taraf paketlerinin kaydedicileri kaydedebileceğini unutmayın. Bu, dikkat edilmesi gereken bir şeydir (ve bazı durumlarda önlenemez). Çoğu durumda, üçüncü taraf kodu, mevcut herhangi bir kök kaydedici işleyicisi olup olmadığını kontrol eder ; ve yoksa - yeni bir konsol işleyicisi kaydederler.

Bu konudaki çözümüm konsol kaydedicimi kök seviyesinde kaydetmekti:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.