Output log duplikat saat menggunakan modul logging Python

105

Saya menggunakan logger python. Berikut ini adalah kode saya:

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

Masalah yang saya miliki adalah saya mendapatkan banyak entri di file log untuk setiap logger.infopanggilan. Bagaimana saya bisa memecahkan masalah ini?

pengguna865438
sumber
Bekerja untuk saya. Python 3.2 dan Windows XP.
Zuljin
2
Apakah Anda yakin Anda tidak membuat beberapa contoh pencatat?
Gandi
Iya. dalam file yang berbeda saya mengambil contoh baru seperti yang kami lakukan di proyek Java. Tolong jelaskan saya apakah itu menimbulkan masalah atau tidak.
user865438

Jawaban:

94

Ini logging.getLogger()sudah menjadi tunggal. ( Dokumentasi )

Masalahnya adalah setiap kali Anda memanggil myLogger(), itu menambahkan penangan lain ke instance, yang menyebabkan log duplikat.

Mungkin sesuatu seperti ini?

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
Werner Smit
sumber
3
Saya pikir Anda harus memiliki loggers.update (dict ((name, logger))) sebagai gantinya.
acrophobia
kenapa loggers.update(dict(name=logger))? tidak loggers[name] = loggerlebih sederhana?
Ryan J McCall
@RyanJMcCall Pada saat itu adalah konvensi pengkodean yang saya gunakan. Tetapi setelah meninjau kode seperti sekarang, saya melihat bahwa itu rusak. loggers.update(dict(name=logger))akan membuat kamus dengan satu kunci yang dipanggil namedan terus memperbarui kunci yang sama. Saya terkejut tidak ada yang tidak menyebutkan ini sebelumnya karena kode ini cukup rusak :) Akan membuat perubahan yang diperlukan.
Werner Smit
Saya melihat @acrophobia telah menghindari ini berabad-abad yang lalu. Terima kasih.
Werner Smit
bukankah loggerskamus global itu berlebihan logging.getLogger? karena Anda benar-benar hanya ingin menghindari penambahan penangan ekstra, sepertinya Anda lebih suka jawaban di bawah ini yang memeriksa penangan secara langsung
mway
60

Sejak Python 3.2 Anda bisa memeriksa apakah penangan sudah ada dan jika demikian, kosongkan mereka sebelum menambahkan penangan baru. Ini cukup nyaman saat debugging dan kode menyertakan inisialisasi logger Anda

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

logger.addHandler(handler)
rm957377
sumber
Jawaban bagus, Thx :))
Gavriel Cohen
2
Perhatikan bahwa hasHandlers () akan mengembalikan nilai true dalam pytest di mana penangan telah ditambahkan ke pencatat akar, meskipun penangan lokal / khusus Anda belum ditambahkan. Len (logger.handlers) (sesuai jawaban Guillaume) akan mengembalikan 0 dalam kasus ini, jadi mungkin opsi yang lebih baik.
Grant
Ini adalah solusi nyata yang saya cari.
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

membuat trik untuk saya

menggunakan python 2.7

Guillaume Cisco
sumber
1
Ini berfungsi bahkan ketika modul dimuat ulang (yang tidak terjadi pada jawaban lain)
yco
3
Terima kasih atas tipnya, BTW untuk memeriksa apakah daftar kosong atau tidak Anda tidak perlu menggunakan operator "len" yang bisa langsung Anda gunakan jika my_list: ..
rkachach
26

Saya sudah menggunakan loggersebagai Singleton dan diperiksa if not len(logger.handlers), tetapi masih mendapat duplikat : Itu adalah keluaran yang diformat, diikuti dengan yang tidak diformat.

Solusi dalam kasus saya: logger.propagate = False

Penghargaan untuk jawaban ini dan dokumen .

Tuan B.
sumber
1
Saya telah mengetahui bahwa pencatatan ganda berasal dari RootLogger dan StreamHandler saya, tetapi tidak dapat menyelesaikan masalah (sambil mempertahankan pemformat saya di StreamHandler) sampai melakukan ini.
Xander YzWich
10

Anda menelepon Logger.myLogger()lebih dari sekali. Menyimpan contoh logger itu kembali di suatu tempat dan menggunakan kembali itu .

Harap diperhatikan juga bahwa jika Anda masuk sebelum penangan ditambahkan, default StreamHandler(sys.stderr)akan dibuat.

Matt Joiner
sumber
Sebenarnya saya mencoba mengakses instance logger seperti yang kami gunakan di java. Tapi saya tidak tahu apakah itu perlu membuat instance hanya sekali untuk keseluruhan proyek atau tidak.
user865438
1
@ user865483: Sekali saja. Semua logger perpustakaan standar adalah lajang.
Matt Joiner
5

Ini adalah tambahan untuk jawaban @ rm957377 tetapi dengan penjelasan mengapa hal ini terjadi . Saat Anda menjalankan fungsi lambda di AWS, mereka memanggil fungsi Anda dari dalam contoh pembungkus yang tetap hidup untuk beberapa panggilan. Artinya, jika Anda memanggil addHandler()dalam kode fungsi Anda, itu akan terus menambahkan penangan duplikat ke singleton logging setiap kali fungsi berjalan. Singleton logging tetap ada melalui beberapa panggilan fungsi lambda Anda.

Untuk mengatasi ini, Anda dapat menghapus penangan Anda sebelum Anda mengaturnya melalui:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)
Chad Befus
sumber
Entah bagaimana dalam kasus saya, penangan logger ditambahkan pada acara .info()panggilan yang saya tidak mengerti.
Evgeny
4

Logger Anda harus bekerja sebagai tunggal. Anda tidak boleh membuatnya lebih dari sekali. Berikut adalah contoh tampilannya:

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")
Zuljin
sumber
sekali lagi jika saya akan mengambil contoh yang berbeda di file yang berbeda. Misalkan di file 1 s = Logger () m = s.myLogger () dan di file 2 s = Logger () Ini akan berfungsi atau tidak m2 = s.myLogger ()
user865438
Masih saya mendapatkan salinan log yang sama beberapa kali. Saya ragu di sini apakah di dalam benang Log mencetak lebih dari satu atau tidak. Tolong bantu saya dalam hal ini.
user865438
1
@ user865438, kita tidak perlu khawatir membuat implementasi tunggal (Ini sudah ada). Untuk masuk ke sub-modul, ikuti tautan Buku Resep Logging resmi . Pada dasarnya Anda harus mengikuti hierarki penamaan saat menamai logger dan sisanya akan ditangani.
narayan
4

Implementasi logger sudah tunggal.

Beberapa panggilan ke logging.getLogger ('someLogger') mengembalikan referensi ke objek logger yang sama. Ini benar tidak hanya dalam modul yang sama, tetapi juga di seluruh modul selama itu dalam proses interpreter Python yang sama. Benar untuk referensi ke objek yang sama; Selain itu, kode aplikasi dapat mendefinisikan dan mengkonfigurasi logger induk dalam satu modul dan membuat (tetapi tidak mengkonfigurasi) logger anak dalam modul terpisah, dan semua panggilan logger ke anak akan diteruskan ke induk. Ini adalah modul utama

Sumber- Menggunakan logging di beberapa modul

Jadi cara Anda memanfaatkan ini adalah -

Misalkan kita telah membuat dan mengkonfigurasi logger yang disebut 'main_logger' di modul utama (yang hanya mengkonfigurasi logger, tidak mengembalikan apa pun).

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

Sekarang di sub-modul, jika kita membuat logger anak mengikuti hierarki penamaan 'main_logger.sub_module_logger' , kita tidak perlu mengkonfigurasinya di sub-modul. Cukup pembuatan logger yang mengikuti hierarki penamaan sudah cukup.

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

Dan itu tidak akan menambahkan penangan duplikat juga.

Lihat pertanyaan ini untuk jawaban yang lebih bertele-tele.

narayan
sumber
1
mendefinisikan ulang penangan setelah getLogger tampaknya berhasil untuk saya: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek
2

Double (atau triple atau ..- berdasarkan jumlah reload) output logger juga dapat terjadi saat Anda memuat ulang modul Anda melalui importlib.reload(untuk alasan yang sama seperti yang dijelaskan dalam jawaban yang diterima). Saya menambahkan jawaban ini hanya untuk referensi di masa mendatang karena saya butuh beberapa saat untuk mencari tahu mengapa keluaran saya adalah dupli (tiga kali lipat).

rkuska
sumber
1

Salah satu solusi sederhana adalah seperti

logger.handlers[:] = [handler]

Dengan cara ini Anda menghindari penambahan penangan baru ke daftar "penangan" yang mendasarinya.

aihex
sumber
1

Intinya untuk sebagian besar kasus ketika ini terjadi, seseorang hanya perlu memanggil logger.getLogger () hanya sekali per modul. Jika Anda memiliki beberapa kelas seperti yang saya lakukan, saya dapat menyebutnya seperti ini:

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')

Keduanya kemudian akan memiliki nama paket lengkap dan metode tempat login.

Harlin
sumber
0

Anda bisa mendapatkan daftar semua penangan untuk logger tertentu, jadi Anda bisa melakukan sesuatu seperti ini

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)

Dalam contoh di atas kami memeriksa apakah penangan untuk file yang ditentukan sudah terhubung ke pencatat, tetapi memiliki akses ke daftar semua penangan memberi Anda kemampuan untuk memutuskan kriteria mana Anda harus menambahkan penangan lain atau tidak.

Paling dicari
sumber
0

Punya masalah ini hari ini. Karena fungsi saya adalah @staticmethod, saran di atas diselesaikan dengan random ().

Tampak seperti:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))
Pacman
sumber
-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
Avinash Kumar
sumber
Mohon tambahkan penjelasan agar jawaban ini lebih berharga untuk penggunaan jangka panjang.
Aminah Nuraini