เอาต์พุตบันทึกที่ซ้ำกันเมื่อใช้โมดูลการบันทึก Python


105

ฉันใช้ python logger ต่อไปนี้เป็นรหัสของฉัน:

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

ปัญหาที่ฉันมีคือฉันได้รับหลายรายการในล็อกไฟล์สำหรับการlogger.infoโทรแต่ละครั้ง ฉันจะแก้ปัญหานี้ได้อย่างไร?


ใช้ได้ผลสำหรับฉัน Python 3.2 และ Windows XP
Zuljin

2
แน่ใจหรือไม่ว่าคุณไม่ได้สร้างอินสแตนซ์ตัวบันทึกหลายรายการ
Gandi

ใช่. ในไฟล์อื่นฉันกำลังใช้อินสแตนซ์ใหม่เหมือนกับที่เราทำในโครงการ Java โปรดระบุฉันว่านั่นเป็นการสร้างปัญหาหรือไม่
user865438

คำตอบ:


94

logging.getLogger()อยู่แล้วเดี่ยว ( เอกสารประกอบ )

ปัญหาคือทุกครั้งที่คุณโทรระบบmyLogger()จะเพิ่มตัวจัดการอื่นในอินสแตนซ์ซึ่งทำให้เกิดการบันทึกซ้ำ

อาจจะเป็นแบบนี้?

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
ฉันคิดว่าคุณควรมี loggers.update (dict ((name, logger))) แทน
acrophobia

เพราะloggers.update(dict(name=logger))อะไร? ไม่loggers[name] = loggerง่ายกว่านี้?
Ryan J McCall

@RyanJMcCall ตอนนั้นมันเป็นรูปแบบการเข้ารหัสที่ฉันใช้ แต่เมื่อตรวจสอบโค้ดตามที่เป็นอยู่ตอนนี้ฉันเห็นว่ามันเสีย loggers.update(dict(name=logger))จะสร้างพจนานุกรมด้วยคีย์เดียวที่เรียกว่าnameและอัปเดตคีย์เดียวกันอย่างต่อเนื่อง ฉันแปลกใจที่ไม่มีใครไม่ได้พูดถึงเรื่องนี้ก่อนหน้านี้เนื่องจากรหัสนี้ค่อนข้างเสีย :) จะทำการแก้ไขที่จำเป็น
Werner Smit

ฉันเห็นว่า @acrophobia เป็นสิ่งที่หลีกเลี่ยงมานานแล้ว ขอบคุณ.
Werner Smit

loggersพจนานุกรมทั่วโลกไม่ซ้ำซ้อนกับlogging.getLogger? เนื่องจากคุณต้องการหลีกเลี่ยงการเพิ่มตัวจัดการเพิ่มเติมดูเหมือนว่าคุณต้องการคำตอบด้านล่างที่ตรวจสอบตัวจัดการโดยตรง
mway

60

เนื่องจาก Python 3.2 คุณสามารถตรวจสอบได้ว่ามีตัวจัดการอยู่แล้วหรือไม่และหากเป็นเช่นนั้นให้ล้างข้อมูลก่อนเพิ่มตัวจัดการใหม่ สิ่งนี้ค่อนข้างสะดวกเมื่อทำการดีบักและรหัสจะรวมถึงการเริ่มต้นตัวบันทึกของคุณ

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

logger.addHandler(handler)

คำตอบที่ดี Thx :))
Gavriel Cohen

3
โปรดทราบว่า hasHandlers () จะคืนค่าจริงใน pytest ซึ่งมีการเพิ่มตัวจัดการลงใน root logger แม้ว่าจะยังไม่ได้เพิ่มตัวจัดการภายใน / กำหนดเองของคุณก็ตาม len (logger.handlers) (ตามคำตอบของ Guillaume) จะคืนค่า 0 ในกรณีนี้ดังนั้นอาจเป็นตัวเลือกที่ดีกว่า
สิทธิ์

นี่คือทางออกที่แท้จริงที่ฉันกำลังมองหา
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

ทำเคล็ดลับสำหรับฉัน

ใช้ python 2.7


1
สิ่งนี้ใช้ได้แม้ในขณะที่โหลดโมดูลใหม่ (ซึ่งไม่ใช่กรณีของคำตอบอื่น ๆ )
yco

3
ขอบคุณสำหรับเคล็ดลับ BTW ในการตรวจสอบว่ารายการว่างเปล่าหรือไม่คุณไม่จำเป็นต้องใช้โอเปอเรเตอร์ "len" ที่คุณสามารถใช้ได้โดยตรงหาก my_list: ..
rkachach

26

ฉันใช้loggerเป็นSingletonแล้วและตรวจสอบif not len(logger.handlers)แล้ว แต่ยังคงมีรายการที่ซ้ำกัน : เป็นเอาต์พุตที่จัดรูปแบบแล้วตามด้วยไฟล์.

วิธีแก้ไขในกรณีของฉัน: logger.propagate = False

เครดิตคำตอบนี้และเอกสาร


1
ฉันพบว่าการบันทึกสองครั้งนั้นมาจาก RootLogger และ StreamHandler ของฉัน แต่ไม่สามารถแก้ไขปัญหาได้ (ในขณะที่เก็บฟอร์แมตเตอร์ของฉันไว้บน StreamHandler) จนกว่าจะทำสิ่งนี้
Xander YzWich

10

คุณกำลังโทรLogger.myLogger()มากกว่าหนึ่งครั้ง เก็บตัวอย่างเช่นคนตัดไม้ก็จะส่งกลับที่ใดที่หนึ่งและนำมาใช้ว่า

โปรดทราบว่าหากคุณเข้าสู่ระบบก่อนที่จะเพิ่มตัวจัดการใด ๆ ระบบStreamHandler(sys.stderr)จะสร้างค่าเริ่มต้น


จริงๆแล้วฉันพยายามเข้าถึงอินสแตนซ์คนตัดไม้เหมือนที่เราใช้ใน java แต่ฉันไม่รู้ว่าจำเป็นต้องสร้างอินสแตนซ์เพียงครั้งเดียวสำหรับทั้งโปรเจ็กต์หรือไม่
user865438

1
@ user865483: แค่ครั้งเดียว ตัวบันทึกไลบรารีมาตรฐานทั้งหมดเป็น singletons
Matt Joiner

5

นี่คือคำตอบนอกจาก @ rm957377 ของแต่มีคำอธิบายว่าทำไมนี้เกิดขึ้น เมื่อคุณเรียกใช้ฟังก์ชันแลมบ์ดาใน AWS พวกเขาจะเรียกใช้ฟังก์ชันของคุณจากภายในอินสแตนซ์การรวมที่ยังคงมีชีวิตอยู่สำหรับการโทรหลายครั้ง หมายความว่าถ้าคุณเรียกaddHandler()ใช้รหัสของฟังก์ชันของคุณระบบจะเพิ่มตัวจัดการที่ซ้ำกันไปยังซิงเกิลตันของการบันทึกทุกครั้งที่ฟังก์ชันทำงาน Singleton การบันทึกยังคงมีอยู่ผ่านการเรียกใช้ฟังก์ชัน lambda ของคุณหลายครั้ง

ในการแก้ปัญหานี้คุณสามารถล้างเครื่องจัดการของคุณก่อนที่จะตั้งค่าผ่าน:

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

อย่างไรก็ตามในกรณีของฉันมีการเพิ่มตัวจัดการคนตัดไม้ในเหตุการณ์ที่.info()เรียกซึ่งฉันไม่เข้าใจ
Evgeny

4

คนตัดไม้ของคุณควรทำงานเป็นซิงเกิลตัน คุณไม่ควรสร้างมากกว่าหนึ่งครั้ง นี่คือตัวอย่างลักษณะที่อาจมี:

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

แล้วอีกครั้งถ้าฉันจะใช้อินสแตนซ์อื่นในไฟล์อื่น สมมติว่าในไฟล์ 1 s = Logger () m = s.myLogger () และในไฟล์ 2 s = Logger () มันจะทำงานหรือไม่ m2 = s.myLogger ()
user865438

ฉันยังคงได้รับสำเนาบันทึกเดียวกันหลายครั้ง ฉันมีข้อสงสัยที่นี่ว่าบันทึกภายในเธรดจะพิมพ์มากกว่าหนึ่งหรือไม่โปรดช่วยฉันในเรื่องนี้
user865438

1
@ user865438 เราไม่จำเป็นต้องกังวลเกี่ยวกับการทำให้การนำไปใช้งานเป็นซิงเกิลตัน (มีอยู่แล้ว) สำหรับการเข้าสู่ระบบย่อยโมดูลตามตำราการบันทึกอย่างเป็นทางการของการเชื่อมโยง โดยทั่วไปคุณต้องทำตามลำดับชั้นการตั้งชื่อในขณะที่ตั้งชื่อคนตัดไม้และจะดูแลส่วนที่เหลือ
narayan

4

การใช้งานคนตัดไม้เป็นแบบซิงเกิลตันอยู่แล้ว

การเรียก logging.getLogger ('someLogger') หลายครั้งส่งคืนการอ้างอิงไปยังอ็อบเจ็กต์ logger เดียวกัน นี่เป็นความจริงไม่เพียง แต่ภายในโมดูลเดียวกันเท่านั้น แต่ยังรวมถึงโมดูลต่างๆด้วยตราบเท่าที่อยู่ในกระบวนการล่าม Python เดียวกัน เป็นจริงสำหรับการอ้างอิงถึงวัตถุเดียวกัน นอกจากนี้รหัสแอปพลิเคชันยังสามารถกำหนดและกำหนดค่าตัวบันทึกแม่ในโมดูลเดียวและสร้าง (แต่ไม่กำหนดค่า) ตัวบันทึกลูกในโมดูลแยกต่างหากและการเรียกตัวบันทึกทั้งหมดไปยังลูกจะส่งผ่านไปยังพาเรนต์ นี่คือโมดูลหลัก

Source- การใช้การเข้าสู่ระบบในหลายโมดูล

ดังนั้นวิธีที่คุณควรใช้คือ -

สมมติว่าเราได้สร้างและกำหนดค่าคนตัดไม้ชื่อ'main_logger'ในโมดูลหลัก (ซึ่งเพียงแค่กำหนดค่าคนตัดไม้ไม่ส่งคืนอะไรเลย)

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

ตอนนี้ในโมดูลย่อยถ้าเราสร้างคนตัดไม้ลูกตามลำดับชั้นการตั้งชื่อ'main_logger.sub_module_logger'เราไม่จำเป็นต้องกำหนดค่าในโมดูลย่อย เพียงแค่สร้างคนตัดไม้ตามลำดับชั้นการตั้งชื่อก็เพียงพอแล้ว

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

และจะไม่เพิ่มตัวจัดการที่ซ้ำกันด้วย

ดูคำถามนี้เพื่อดูคำตอบเพิ่มเติมเล็กน้อย


1
การกำหนดตัวจัดการใหม่หลังจาก getLogger ดูเหมือนจะใช้ได้กับฉัน: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

เอาต์พุตตัวบันทึกสองครั้ง (หรือสามครั้งหรือ .. - ขึ้นอยู่กับจำนวนการโหลดซ้ำ) อาจเกิดขึ้นเมื่อคุณโหลดโมดูลของคุณใหม่ผ่านimportlib.reload(ด้วยเหตุผลเดียวกับที่อธิบายไว้ในคำตอบที่ยอมรับ) ฉันกำลังเพิ่มคำตอบนี้สำหรับการอ้างอิงในอนาคตเนื่องจากฉันต้องใช้เวลาสักพักในการหาคำตอบว่าเหตุใดผลลัพธ์ของฉันจึงเป็น dupli (triple) cated


1

วิธีแก้ปัญหาง่ายๆอย่างหนึ่งก็เช่น

logger.handlers[:] = [handler]

วิธีนี้จะช่วยหลีกเลี่ยงการต่อท้ายตัวจัดการใหม่ในรายการ "ตัวจัดการ" ที่อยู่ภายใต้


1

บรรทัดล่างสุดสำหรับกรณีส่วนใหญ่เมื่อเกิดเหตุการณ์นี้ต้องเรียก logger.getLogger () เพียงครั้งเดียวต่อโมดูล ถ้าคุณมีหลายคลาสเหมือนฉันฉันจะเรียกมันว่า:

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

จากนั้นทั้งสองจะมีชื่อแพ็กเกจเต็มและวิธีการที่บันทึกไว้


0

คุณสามารถรับรายชื่อตัวจัดการทั้งหมดสำหรับคนตัดไม้โดยเฉพาะดังนั้นคุณสามารถทำสิ่งนี้ได้

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)

ในตัวอย่างด้านบนเราตรวจสอบว่าตัวจัดการสำหรับไฟล์ที่ระบุนั้นเชื่อมต่อกับคนตัดไม้แล้วหรือไม่ แต่การเข้าถึงรายการของตัวจัดการทั้งหมดทำให้คุณสามารถตัดสินใจได้ว่าคุณควรเพิ่มตัวจัดการอื่นหรือไม่


0

มีปัญหานี้ในวันนี้ เนื่องจากฟังก์ชันของฉันเป็น @staticmethod คำแนะนำข้างต้นจึงได้รับการแก้ไขด้วยการสุ่ม ()

กำลังมองหาสิ่งที่ชอบ:

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

โปรดเพิ่มคำอธิบายเพื่อให้คำตอบนี้มีคุณค่ายิ่งขึ้นสำหรับการใช้งานในระยะยาว
Aminah Nuraini
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.