Python günlük kaydı modülünü kullanırken yinelenen günlük çıkışı


106

Python logger kullanıyorum. Aşağıdaki kodum:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Karşılaştığım sorun, her logger.infoarama için günlük dosyasında birden çok girdi almam . Bunu Nasıl Çözebilirim?


Benim için çalışıyor. Python 3.2 ve Windows XP.
Zuljin

2
Birden fazla kaydedici örneği oluşturmadığınızdan emin misiniz?
Gandi

Evet. Java projelerinde yaptığımız gibi farklı bir dosyada yeni örnek alıyorum. Lütfen bunun sorun yaratıp yaratmadığını belirtin.
user865438

Yanıtlar:


95

logging.getLogger()Zaten bir tekil olduğunu. ( Belgeler )

Sorun şu ki, her aradığınızda myLogger(), örneğe başka bir işleyici ekliyor ve bu da yinelenen günlüklere neden oluyor.

Belki bunun gibi bir şey?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
Bunun yerine loggers.update (dict ((name, logger))) kullanmanız gerektiğini düşünüyorum.
acrophobia

neden loggers.update(dict(name=logger))? loggers[name] = loggerdaha basit değil mi?
Ryan J McCall

@RyanJMcCall O zamanlar kullandığım kodlama kuralıydı. Ancak kodu şu an olduğu gibi gözden geçirdikten sonra, bozuk olduğunu görüyorum. loggers.update(dict(name=logger))tek tuşla bir sözlük oluşturacak nameve aynı anahtarı sürekli güncelleyecektir. Bu kod oldukça bozuk olduğu için kimsenin bundan daha önce bahsetmemesine şaşırdım :) Gerekli değişiklikleri yapacak.
Werner Smit

Görüyorum ki @ akrofobi bu çağlar önce kaçıyordu. Teşekkürler.
Werner Smit

global loggerssözlük gereksiz değil logging.getLoggermi? gerçekten sadece ek işleyici eklemekten kaçınmak istediğiniz için, aşağıdaki yanıtları doğrudan işleyicileri kontrol etmeyi tercih edersiniz gibi görünüyor
mway

62

Python 3.2'den beri, işleyicilerin zaten mevcut olup olmadığını kontrol edebilir ve varsa, yeni işleyiciler eklemeden önce bunları temizleyebilirsiniz. Bu, hata ayıklama sırasında oldukça kullanışlıdır ve kod, kaydedici başlatmanızı içerir

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

Güzel cevap, Thx :))
Gavriel Cohen

3
Yerel / özel işleyicileriniz henüz eklenmemiş olsa bile hasHandlers () işlevinin, kök günlükçüye bir işleyicinin eklendiği pytest'te true döndürecektir. Len (logger.handlers) (Guillaume'un cevabına göre) bu durumda 0 döndürecektir, bu yüzden daha iyi bir seçenek olabilir.
Grant

Bu, aradığım gerçek çözüm.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

benim için numara yaptı

python 2.7 kullanarak


1
Bu, modül yeniden yüklendiğinde bile çalışır (bu, diğer cevaplar için geçerli değildir)
yco

3
Bahşiş için teşekkürler, BTW bir listenin boş olup olmadığını kontrol etmek için doğrudan kullanabileceğiniz "len" operatörünü kullanmanız gerekmiyorsa my_list: ..
rkachach

27

Zaten loggerbir Singleton olarak kullandım ve kontrol ettim if not len(logger.handlers), ancak yine de kopyalar aldım : Biçimlendirilmiş çıktı, ardından biçimlendirilmemiş çıktı.

Benim durumumdaki çözüm : logger.propagate = False

Bu cevaba ve belgelere kredi .


1
İki katına çıkarılan günlük kaydının RootLogger ve StreamHandler'ımdan olduğunu anladım, ancak sorunu çözemedim (biçimlendiricimi StreamHandler'da tutarken) bunu yapana kadar.
Xander YzWich

10

Aradığınız Logger.myLogger()kereden fazla. Bir yerde döner logger örneğini saklayın ve yeniden o .

Herhangi bir işleyici eklenmeden önce oturum açarsanız, bir varsayılan StreamHandler(sys.stderr)oluşturulacağını da unutmayın.


Aslında java'da kullandığımız gibi logger örneğine erişmeye çalışıyorum ancak tüm proje için yalnızca bir kez bir örnek oluşturmaya gerek olup olmadığını bilmiyorum.
user865438

1
@ user865483: Yalnızca bir kez. Tüm standart kitaplık kaydediciler tekildir.
Matt Joiner

5

Logger uygulaması zaten bir singleton'dur.

Logging.getLogger ('someLogger') öğesine yapılan birden çok çağrı, aynı günlükçü nesnesine bir başvuru döndürür. Bu sadece aynı modül içinde değil, aynı Python yorumlayıcı sürecinde olduğu sürece modüller için de geçerlidir. Aynı nesneye yapılan göndermeler için doğrudur; ek olarak, uygulama kodu bir modülde bir ana kaydediciyi tanımlayabilir ve yapılandırabilir ve ayrı bir modülde bir çocuk kaydedici oluşturabilir (ancak yapılandırmaz) ve çocuğa yapılan tüm günlükçü çağrıları üst öğeye aktarılır. İşte bir ana modül

Kaynak - Birden çok modülde günlük kaydı kullanma

Öyleyse bundan yararlanmanız gereken yol -

Ana modülde 'main_logger' adında bir kaydedici oluşturduğumuzu ve yapılandırdığımızı varsayalım (bu, basitçe kaydediciyi yapılandırır, hiçbir şey döndürmez).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Şimdi bir alt modülde, 'main_logger.sub_module_logger' adlandırma hiyerarşisini izleyen bir çocuk günlükçü oluşturursak , onu alt modülde yapılandırmamız gerekmez. Adlandırma hiyerarşisini takip eden kaydedicinin oluşturulması yeterlidir.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

Ve aynı zamanda yinelenen işleyici eklemeyecektir.

Biraz daha ayrıntılı yanıt için bu soruya bakın .


1
getLogger'dan sonra işleyicileri yeniden tanımlamak benim için işe yarıyor gibi görünüyor: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

5

Bu, @ rm957377'nin cevabına bir ektir, ancak bunun neden olduğuna dair bir açıklama içerir . AWS'de bir lambda işlevi çalıştırdığınızda, işlevinizi birden çok çağrı için canlı kalan bir sarmalama örneği içinden çağırırlar. Yani, addHandler()işlevinizin kodu içinde arama yaparsanız , işlev her çalıştığında günlük tekiline yinelenen işleyiciler eklemeye devam edecektir. Günlüğe kaydetme tekli, lambda işlevinizin birden çok çağrısı boyunca devam eder.

Bunu çözmek için işleyicilerinizi şu yolla ayarlamadan önce temizleyebilirsiniz:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

Her nasılsa benim durumumda kaydedici işleyicileri, .info()anlamadığım çağrı üzerine olayda ekleniyor .
Evgeny

4

Kaydediciniz singleton olarak çalışmalıdır. Bunu birden fazla oluşturmamalısın. İşte nasıl görünebileceğine dair bir örnek:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

sonra tekrar farklı bir dosyada farklı örneği alacağım. 1 s = Logger () m = s.myLogger () dosyasında ve 2 s = Logger () dosyasında çalışacağını veya çalışmayacağını varsayalım m2 = s.myLogger ()
user865438

Yine de aynı günlüğün kopyasını birkaç kez alıyorum. Burada Log iş parçacığının birden fazla yazdırıp yazdırmadığına dair bir şüphem var. Lütfen bu konuda bana yardım edin.
user865438

1
@ user865438, uygulamayı bir singleton yapma konusunda endişelenmemize gerek yok (Zaten öyle). Alt modüllerde oturum açmak için, resmi Oturum Açma Yemek Kitabı bağlantısını izleyin . Temel olarak, kaydedicileri adlandırırken adlandırma hiyerarşisini izlemeniz gerekir ve gerisini o halleder.
narayan

2

Çift (veya üçlü veya ..- yeniden yükleme sayısına bağlı olarak) günlükçü çıkışı, modülünüzü aracılığıyla yeniden yüklediğinizde de gerçekleşebilir importlib.reload(kabul edilen yanıtta açıklananla aynı nedenle). Çıktımın neden dupli (üçlü) katılı olduğunu anlamam biraz zaman aldığından, bu yanıtı sadece gelecekteki bir referans için ekliyorum.


1

Basit bir çözüm şudur:

logger.handlers[:] = [handler]

Bu şekilde, temeldeki "işleyiciler" listesine yeni işleyici eklemekten kaçınmış olursunuz.


1

Alt satırda, çoğu durumda bu olduğunda, sadece modül başına logger.getLogger () 'ı çağırmak yeterlidir. Benim yaptığım gibi birden fazla sınıfınız varsa, şöyle diyebilirim:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Her ikisi de kendi tam paket adına ve günlüğe kaydedildiği yerde yöntemine sahip olacaktır.


0

Belirli bir kaydedici için tüm işleyicilerin listesini alabilirsiniz, böylece böyle bir şey yapabilirsiniz

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

Yukarıdaki örnekte, belirtilen bir dosya için işleyicinin zaten kaydediciye bağlı olup olmadığını kontrol ediyoruz, ancak tüm işleyicilerin listesine erişiminiz, başka bir işleyici ekleyip eklememeniz gerektiğine karar verebilmenizi sağlar.


0

Bugün bu sorunu yaşadım. Fonksiyonlarım @staticmethod olduğundan, yukarıdaki öneriler random () ile çözüldü.

Şuna benziyor:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

Lütfen bu cevabı uzun süreli kullanım için daha değerli hale getirmek için açıklama ekleyin.
Aminah Nuraini
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.