पायथन लॉगिंग मॉड्यूल का उपयोग करते समय डुप्लिकेट लॉग आउटपुट


105

मैं अजगर लकड़हारे का उपयोग कर रहा हूं। निम्नलिखित मेरा कोड है:

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कॉल के लिए लॉग फ़ाइल में कई प्रविष्टियाँ मिलती हैं। इसे कैसे हल किया जा सकता है?


मेरे लिये कार्य करता है। पायथन 3.2 और विंडोज एक्सपी।
— ज़ुलजिन

2
क्या आप सुनिश्चित हैं कि आप कई लकड़हारे उदाहरण नहीं बनाते हैं?
— गान्धी

हाँ। अलग-अलग फ़ाइल में मैं नया उदाहरण ले रहा हूं जैसा कि हमने जावा प्रोजेक्ट्स में किया था। कृपया मुझे बताएं कि क्या समस्या पैदा हो रही है या नहीं।
— 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 (तानाशाही (नाम, लकड़हारा)) होना चाहिए।
— acrophobia

क्यों loggers.update(dict(name=logger))? loggers[name] = loggerसरल नहीं है?
— रयान जे मैककॉल

@RyanJMcCall उस समय मैंने जिस कोडिंग कन्वेंशन का इस्तेमाल किया था। लेकिन कोड की समीक्षा के रूप में यह अब है, मुझे लगता है कि यह टूट गया है। loggers.update(dict(name=logger))एक एकल कुंजी के साथ एक शब्दकोश बनाने के लिए कहा जाता है nameऔर लगातार उसी कुंजी को अद्यतन करेगा। मुझे आश्चर्य है कि किसी ने भी पहले इसका उल्लेख नहीं किया है क्योंकि यह कोड काफी टूट गया है :) आवश्यक संशोधन करेगा।
— वर्नर स्मिट

मुझे लगता है कि @acrophobia इस युग से पहले था। धन्यवाद।
— वर्नर स्मिट

क्या वैश्विक loggersशब्दकोष बेमानी नहीं है logging.getLogger? चूँकि आप वास्तव में सिर्फ अतिरिक्त हैंडलर जोड़ने से बचना चाहते हैं, ऐसा लगता है कि आप नीचे दिए गए उत्तरों को पसंद करेंगे जो सीधे हैंडलर्स के लिए चेक करें
— mway

60

पाइथन 3.2 के बाद से आप सिर्फ यह देख सकते हैं कि क्या हैंडलर पहले से मौजूद हैं और यदि हां, तो नए हैंडलर जोड़ने से पहले उन्हें साफ करें। यह बहुत सुविधाजनक है जब डिबगिंग और कोड में आपके लकड़हारा आरंभीकरण शामिल है

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

logger.addHandler(handler)

अच्छा जवाब, Thx :))
— गैवरियल कोहेन

2
ध्यान दें कि hasHandlers () पाइटेस्ट में सही वापस आएगा जहां रूट लॉगर में एक हैंडलर जोड़ा गया है, भले ही आपके स्थानीय / कस्टम हैंडलर को अभी तक जोड़ा नहीं गया हो। लेन (logger.handlers) (गिलियूम के जवाब के अनुसार) इस मामले में 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

मेरे लिए चाल बना दी

अजगर 2.7 का उपयोग करना


1
मॉड्यूल के पुनः लोड होने पर भी यह काम करता है (जो अन्य उत्तरों का मामला नहीं है)
— yco

3
टिप के लिए धन्यवाद, बीटीडब्ल्यू यह जांचने के लिए कि क्या कोई सूची खाली है या नहीं, आपको "लेन" ऑपरेटर का उपयोग करने की आवश्यकता नहीं है यदि आप सीधे उपयोग कर सकते हैं यदि my_list: ..
— rachachach

26

मैंने पहले से loggerही एक सिंगलटन के रूप में इस्तेमाल किया और जांच की if not len(logger.handlers), लेकिन अभी भी डुप्लिकेट मिला : यह स्वरूपित आउटपुट था, इसके बाद बिना किसी बदलाव के।

मेरे मामले में समाधान : logger.propagate = False

इस उत्तर और डॉक्स का श्रेय ।


1
मुझे पता चला था कि रुट्ड लॉगिंग रूटलॉगर और मेरे स्ट्रीमहैंडलर से था, लेकिन ऐसा करने तक समस्या को हल नहीं कर सका (जबकि स्ट्रीमहैंडलर पर अपने फॉर्मेटर को रखते हुए)।
— Xander YzWich

10

आप Logger.myLogger()एक से अधिक बार कॉल कर रहे हैं । लकड़हारा उदाहरण स्टोर करें यह कहीं लौटता है और पुन: उपयोग करता है ।

यह भी सलाह दी जाती है कि यदि आप किसी भी हैंडलर को जोड़ने से पहले लॉग इन करते हैं, तो एक डिफ़ॉल्ट StreamHandler(sys.stderr)बनाया जाएगा।


वास्तव में मैं लकड़हारा उदाहरण का उपयोग करने की कोशिश कर रहा हूं जैसा कि हम जावा में उपयोग करते हैं। लेकिन मुझे नहीं पता कि यह एक पूरे प्रोजेक्ट के लिए केवल एक बार एक उदाहरण बनाने की आवश्यकता है या नहीं।
— user865438

1
@ user865483: बस एक बार। सभी मानक लाइब्रेरी लॉगर एकल हैं।
— मैट जॉइनर

5

यह @ rm957377 के उत्तर के अतिरिक्त है लेकिन ऐसा क्यों हो रहा है, इसकी व्याख्या के साथ । जब आप AWS में एक लंबो फ़ंक्शन चलाते हैं, तो वे आपके फ़ंक्शन को एक रैपिंग इंस्टेंस के भीतर से कॉल करते हैं जो कई कॉल के लिए जीवित रहता है। मतलब, यदि आप addHandler()अपने फ़ंक्शन के कोड के भीतर कॉल करते हैं, तो यह फ़ंक्शन चलाने पर हर बार लॉगिंग सिंगलटन में डुप्लिकेट हैंडलर जोड़ना जारी रखेगा। लॉगिंग सिंगलटन, लैम्बडा फंक्शन के कई कॉल्स के माध्यम से बना रहता है।

इसे हल करने के लिए आप अपने हैंडलर को अपने द्वारा सेट करने से पहले साफ़ कर सकते हैं

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

किसी तरह मेरे मामले में लकड़हारा संचालकों को .info()कॉल पर घटना में जोड़ा जाता है जो मुझे समझ में नहीं आता है।
— एवगेनी

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 = लकड़हारा () m = s.myLogger () और फ़ाइल 2 s = लकड़हारा () यह काम करेगा या नहीं m2 = s.myLogger ()
— user865438

फिर भी मुझे कई बार एक ही लॉग की कॉपी मिल रही है। मुझे यहां एक संदेह है कि क्या थ्रेड लॉग लॉग एक से अधिक प्रिंट करता है या नहीं। कृपया इसमें मेरी मदद करें।
— user865438

1
@ user865438, हमें कार्यान्वयन को एक सिंगलटन बनाने के बारे में चिंता करने की आवश्यकता नहीं है (यह पहले से ही है)। उप-मॉड्यूल में लॉगिंग के लिए, आधिकारिक लॉगिंग कुकबुक लिंक का पालन करें । मूल रूप से आपको लॉगर्स का नामकरण करते समय नामकरण पदानुक्रम का पालन करने की आवश्यकता है और यह बाकी का ख्याल रखता है।
— narayan

4

लकड़हारा का कार्यान्वयन पहले से ही एक सिंगलटन है।

Logging.getLogger ('someLogger') के लिए एकाधिक कॉल उसी लकड़हारे ऑब्जेक्ट के लिए एक संदर्भ लौटाते हैं। यह न केवल एक ही मॉड्यूल के भीतर, बल्कि पूरे मॉड्यूल में भी सही है क्योंकि यह एक ही पायथन इंटरप्रिटर प्रक्रिया में है। यह उसी वस्तु के संदर्भ के लिए सही है; इसके अतिरिक्त, एप्लिकेशन कोड एक मॉड्यूल में एक माता-पिता लकड़हारे को परिभाषित और कॉन्फ़िगर कर सकता है और एक अलग मॉड्यूल में एक बच्चा लकड़हारा बना सकता है (लेकिन कॉन्फ़िगर नहीं कर सकता है), और बच्चे के लिए सभी लकड़हारा कॉल माता-पिता को पारित करेंगे। यहाँ एक मुख्य मॉड्यूल है

स्रोत- कई मॉड्यूल में लॉगिंग का उपयोग करना

तो जिस तरह से आपको इसका उपयोग करना चाहिए वह है -

मान लें कि हमने मुख्य मॉड्यूल में '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उसी के माध्यम से लोड करते हैं (उसी कारण जैसे कि स्वीकृत उत्तर में समझाया गया है)। मैं इस उत्तर को भविष्य के संदर्भ के लिए जोड़ रहा हूं क्योंकि मुझे यह पता लगाने में थोड़ा समय लगा कि मेरा आउटपुट दुपली (ट्रिपल) क्यों है।


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

कृपया इस उत्तर को दीर्घकालिक उपयोग के लिए अधिक मूल्यवान बनाने के लिए स्पष्टीकरण जोड़ें।
— अमीना नूरैनी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.