pesan log muncul dua kali dengan Python Logging

100

Saya menggunakan Python logging, dan untuk beberapa alasan, semua pesan saya muncul dua kali.

Saya memiliki modul untuk mengonfigurasi logging:

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

Nanti, saya memanggil metode ini untuk mengkonfigurasi logging:

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

Dan kemudian katakanlah, modul buy_ham, saya akan menelepon:

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

Dan untuk beberapa alasan, semua pesan muncul dua kali. Saya mengomentari salah satu penangan arus, masih hal yang sama. Agak aneh, tidak yakin mengapa ini terjadi ... lol. Dengan asumsi saya melewatkan sesuatu yang jelas.

Cheers, Victor

victorhooi
sumber
1
Apakah Anda yakin configure_logging()tidak dipanggil dua kali (misalnya dari konstruktor juga)? Apakah hanya satu instance Boy () yang dibuat?
Jacek Konieczny
1
Menggunakan self.logger.handlers = [ch]sebagai gantinya akan menyelesaikan masalah ini, meskipun akan lebih baik hanya memastikan bahwa Anda tidak menjalankan kode ini dua kali, misalnya, menggunakan if not self.loggerdi awal.
Ninjakannon

Jawaban:

134

Anda memanggil configure_loggingdua kali (mungkin dalam __init__metode Boy): getLoggerakan mengembalikan objek yang sama, tetapi addHandlertidak memeriksa apakah penangan serupa telah ditambahkan ke logger.

Coba lacak panggilan ke metode itu dan hilangkan salah satunya. Atau atur tanda yang logging_initializeddiinisialisasi ke Falsedalam __init__metode Boydan ubah configure_loggingmenjadi tidak melakukan apa pun jika logging_initializedada True, dan setel ke Truesetelah Anda menginisialisasi logger.

Jika program Anda membuat beberapa Boycontoh, Anda harus mengubah cara Anda melakukan sesuatu dengan configure_loggingfungsi global menambahkan penangan, dan Boy.configure_loggingmetode hanya menginisialisasi self.loggeratribut.

Cara lain untuk menyelesaikan ini adalah dengan memeriksa atribut penangan dari logger Anda:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)
brankar alex
sumber
1
Ya, Anda benar - saya konyol. Saya menyebutnya init , serta secara eksplisit di tempat lain. Lol. Terima kasih =).
victorhooi
Terima kasih. Solusi Anda menyelamatkan saya hari ini.
ForeverLearner
1
Dalam kasus saya, mereka muncul 6 kali. Saya sudah curiga karena saya telah menyatakan jenis logger yang sama di 6 kelas oop
answerSeeker
5
Saya ingin berbagi pengalaman saya di sini: untuk aplikasi Flask yang saya kembangkan, pesan log muncul LEBIH DARI DUA KALI. Saya akan mengatakan bahwa mereka bertambah pada file log, karena fakta bahwa, ketika aplikasi dan modul dimuat, loggervariabel yang digunakan, bukan yang dipakai dari salah satu kelas saya, tetapi loggervariabel yang ada di cache Python3 , dan penangan ditambahkan setiap 60 detik oleh AppScheduler yang saya konfigurasikan. Jadi, ini if not logger.handlersadalah cara yang cukup cerdas untuk menghindari fenomena semacam ini. Terima kasih atas solusinya, kawan :)!
ivanleoncz
2
Saya melihat masalah ini di aplikasi Flask saya. Solusi ini memperbaiki masalah untuk pesan log yang dibuat di aplikasi flask utama, tetapi aplikasi saya berfungsi dalam modul perpustakaan, dan pesan-pesan dari perpustakaan itu masih dicatat beberapa kali. Saya tidak tahu bagaimana cara memperbaikinya.
Cas
24

Jika Anda melihat masalah ini dan Anda tidak menambahkan handler dua kali, lihat jawaban abarnert di sini

Dari dokumen :

Catatan: Jika Anda memasang handler ke sebuah logger dan satu atau lebih dari leluhurnya, itu mungkin mengeluarkan catatan yang sama beberapa kali. Secara umum, Anda tidak perlu memasang penangan ke lebih dari satu pencatat - jika Anda hanya melampirkannya ke pencatat yang sesuai yang tertinggi dalam hierarki pencatat, maka itu akan melihat semua peristiwa yang dicatat oleh semua penebang turunan, asalkan mereka menyebarkan pengaturan dibiarkan disetel ke True. Skenario umum adalah memasang penangan hanya ke root logger, dan membiarkan propagasi mengurus sisanya.

Jadi, jika Anda menginginkan penangan khusus pada "test", dan Anda tidak ingin pesannya juga masuk ke penangan root, jawabannya sederhana: matikan tanda propagasi:

logger.propagate = Salah

landak gila
sumber
1
Itulah jawaban terbaiknya. Itu tidak sesuai dengan tujuan poster (kesalahan logis dalam pengkodean) tetapi seringkali, ini harus terjadi.
Artem
Bravo. INI adalah penyebab sebenarnya dari duplikat (untuk kasus yang paling umum).
Tn. Duhart
Ini juga masalah dengan kode saya. Terima kasih banyak.
harshit
Jawaban terbaik yang pernah ada. Terima kasih!
Foivos Ts
8

Pawang ditambahkan setiap kali Anda menelepon dari luar. Coba Lepaskan Handler setelah Anda menyelesaikan pekerjaan Anda:

self.logger.removeHandler(ch)
Mayukh Roy
sumber
1
Saya menggunakan logger.handlers.pop() python 2.7, melakukan trik
radtek
6

Saya pemula python, tetapi ini sepertinya berhasil untuk saya (Python 2.7)

while logger.handlers:
     logger.handlers.pop()
Gregory Ponto
sumber
4

Dalam kasus saya, saya akan mengatur logger.propagate = Falseuntuk mencegah pencetakan ganda.

Dalam kode di bawah ini jika Anda menghapus logger.propagate = Falsemaka Anda akan melihat pencetakan ganda.

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')
Shital Shah
sumber
Ini adalah masalah yang saya miliki. Terima kasih
q0987
Jawaban yang bagus; menambahkan logger.propagate = Falseadalah solusi untuk mencegah pencatatan ganda dalam aplikasi Flask yang dihosting oleh Pelayan, saat masuk ke app.loggerinstance Flask .
bluebinary
1

Panggilan untuk logging.debug()memanggil logging.basicConfig()jika tidak ada penangan root yang diinstal. Itu terjadi pada saya dalam kerangka pengujian di mana saya tidak dapat mengontrol urutan kasus uji yang dijalankan. Kode inisialisasi saya sedang menginstal yang kedua. Defaultnya menggunakan logging.BASIC_FORMAT yang tidak saya inginkan.

JimB
sumber
Saya pikir inilah yang terjadi pada saya. Bagaimana Anda mencegah pembuatan otomatis logger konsol?
Robert
@Robert ini tentang memastikan Anda diinisialisasi dengan logger yang Anda inginkan, sebelum panggilan logging pertama. Kerangka pengujian dapat mengaburkan hal ini, tetapi harus ada cara untuk melakukannya. Selain itu, jika Anda melakukan multiproses, Anda harus melakukan hal yang sama dengan setiap proses.
JimB
1

Tampaknya jika Anda mengeluarkan sesuatu ke logger (secara tidak sengaja) kemudian mengkonfigurasinya, itu sudah terlambat. Misalnya, dalam kode saya, saya punya

logging.warning("look out)"

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

root.info("hello")

Saya akan mendapatkan sesuatu seperti (mengabaikan opsi format)

look out
hello
hello

dan semuanya ditulis untuk stdout dua kali. Saya percaya ini karena panggilan pertama untuk logging.warningmembuat penangan baru secara otomatis, dan kemudian saya secara eksplisit menambahkan penangan lain. Masalahnya hilang ketika saya menghapus logging.warningpanggilan pertama yang tidak disengaja .

Mark Lakata
sumber
0

Saya mendapatkan situasi yang aneh di mana log konsol digandakan tetapi log file saya tidak. Setelah banyak menggali, saya menemukan jawabannya.

Perlu diketahui bahwa paket pihak ketiga dapat mendaftarkan penebang. Ini adalah sesuatu yang harus diperhatikan (dan dalam beberapa kasus tidak dapat dicegah). Dalam banyak kasus, kode pihak ketiga memeriksa untuk melihat apakah ada penangan root logger yang ada; dan jika tidak ada - mereka mendaftarkan penangan konsol baru.

Solusi saya untuk ini adalah mendaftarkan logger konsol saya di tingkat root:

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)
Robert
sumber