2025, Oct 22 15:47

Python logging में INFO और ERROR अलग रखें: propagate को false करें

Python logging में INFO/ERROR को अलग फ़ाइलों में लिखते समय डुप्लीकेट क्यों आते हैं, propagate का असर क्या है, और dictConfig में इसे false सेट कर समस्या रोकें।

INFO और ERROR लॉग्स को अलग-अलग फ़ाइलों में बाँटना पहली नज़र में सरल लगता है, लेकिन Python logging की एक बारीक कॉन्फ़िगरेशन वजह से ERROR प्रविष्टियाँ INFO फ़ाइल में भी दिख सकती हैं। अगर आपको कंसोल पर डुप्लीकेट पंक्तियाँ दिख रही हैं और आपकी त्रुटि-सूचनाएँ app_error.log के साथ-साथ app_info.log में भी लिखी जा रही हैं, तो कारण लगभग निश्चित रूप से वही है: propagation वास्तव में बंद नहीं है।

समस्या का सेटअप

मान लीजिए, आपकी लॉगिंग कॉन्फ़िगरेशन में आप INFO संदेशों को app_info.log और ERROR संदेशों को app_error.log में भेजना चाहते हैं। नीचे दिया गया कॉन्फ़िग इसी मंशा को दर्शाता है, फिर भी ERROR प्रविष्टियाँ दोनों फ़ाइलों में दिखाई देती हैं।

{
  "version": 1,
  "disable_existing_loggers": false,
  "formatters": {
    "fmt_line": {
      "format": "%(asctime)s - %(name)s -  %(levelname)s - %(message)s"
    }
  },
  "handlers": {
    "InfoSink": {
      "class": "logging.handlers.RotatingFileHandler",
      "filename": "app_info.log",
      "level": "INFO",
      "formatter": "fmt_line",
      "maxBytes": 10485760,
      "backupCount": 5,
      "encoding": "utf-8"
    },
    "ErrorSink": {
      "class": "logging.handlers.RotatingFileHandler",
      "filename": "app_error.log",
      "level": "ERROR",
      "formatter": "fmt_line",
      "maxBytes": 10485760,
      "backupCount": 5,
      "encoding": "utf-8"
    },
    "ConsoleSink": {
      "class": "logging.StreamHandler",
      "level": "INFO",
      "formatter": "fmt_line",
      "stream": "ext://sys.stdout"
    }
  },
  "loggers": {
    "svc_ERR": {
      "handlers": ["ConsoleSink", "ErrorSink"],
      "level": "ERROR",
      "propagate": "no"
    },
    "svc_INFO": {
      "handlers": ["ConsoleSink", "InfoSink"],
      "level": "INFO",
      "propagate": "no"
    }
  },
  "root": {
    "level": "DEBUG",
    "handlers": ["ConsoleSink", "InfoSink"]
  }
}

इस कॉन्फ़िगरेशन के लिए एक न्यूनतम लोडर कुछ ऐसा दिख सकता है:

import logging
import logging.config
import json
CFG_PATH = "log_config.json"
active_log = None
def start_logger(alias):
    global active_log
    with open(CFG_PATH, "r") as fh:
        cfg_obj = json.load(fh)
    if cfg_obj is None:
        raise ValueError("initialize log json file is not set")
    logging.config.dictConfig(cfg_obj)
    active_log = logging.getLogger(alias)
    return active_log

और इसका उपयोग:

>>> import logger_bootstrap  # your module containing start_logger
>>> lg = logger_bootstrap.start_logger("svc_ERR")
>>> lg.error("this is test2")

भले ही आप error लॉगर को टार्गेट कर रहे हों, त्रुटि संदेश app_error.log के साथ-साथ app_info.log में भी लिखा जाता है। इसके अलावा, जब नामित लॉगर और root लॉगर दोनों stdout पर लिखते हैं, तो कंसोल में डुप्लीकेट पंक्तियाँ भी दिखती हैं।

यह क्यों होता है

यहाँ दो बातें काम करती हैं। पहला, INFO पर सेट किया गया हैंडलर INFO या उससे उच्च स्तर के रिकॉर्ड स्वीकार करता है, जिसमें ERROR भी शामिल है। दूसरा, कोई रिकॉर्ड “बबल अप” करके पूर्वज लॉगर्स तक जाएगा या नहीं, यह propagate गुण पर निर्भर करता है। कुंजी यह है कि इस गुण की व्याख्या कैसे होती है।

यदि यह गुण true पर मूल्यांकित होता है, तो इस लॉगर में दर्ज घटनाएँ, इस लॉगर से जुड़े किसी भी हैंडलर के अतिरिक्त, उच्च-स्तरीय (पूर्वज) लॉगर्स के हैंडलर्स तक भी भेजी जाएँगी।

ऊपर की कॉन्फ़िगरेशन में propagate एक non-empty स्ट्रिंग के रूप में परिभाषित है, जैसे "no"। कोई भी non-empty स्ट्रिंग True मानी जाती है। यानी propagation वास्तव में बंद नहीं है, और svc_ERR के रिकॉर्ड root लॉगर तक पहुँच जाते हैं, जिसके साथ InfoSink जुड़ा है। चूँकि INFO का अर्थ “INFO या उससे ऊपर” होता है, root भी उस ERROR रिकॉर्ड को app_info.log में लिख देता है।

इस स्ट्रिंग को किसी दूसरी non-empty स्ट्रिंग, जैसे "false", से बदलने पर भी नतीजा नहीं बदलेगा; वह अब भी True गिनी जाएगी। propagation को सच में बंद करने के लिए इस गुण का मान boolean false, 0 या खाली स्ट्रिंग होना चाहिए।

उपाय

propagate के लिए वास्तविक boolean का उपयोग करें। नीचे सुधरा हुआ कॉन्फ़िगरेशन दिया है; बाकी किसी चीज़ को बदलने की ज़रूरत नहीं।

{
  "version": 1,
  "disable_existing_loggers": false,
  "formatters": {
    "fmt_line": {
      "format": "%(asctime)s - %(name)s -  %(levelname)s - %(message)s"
    }
  },
  "handlers": {
    "InfoSink": {
      "class": "logging.handlers.RotatingFileHandler",
      "filename": "app_info.log",
      "level": "INFO",
      "formatter": "fmt_line",
      "maxBytes": 10485760,
      "backupCount": 5,
      "encoding": "utf-8"
    },
    "ErrorSink": {
      "class": "logging.handlers.RotatingFileHandler",
      "filename": "app_error.log",
      "level": "ERROR",
      "formatter": "fmt_line",
      "maxBytes": 10485760,
      "backupCount": 5,
      "encoding": "utf-8"
    },
    "ConsoleSink": {
      "class": "logging.StreamHandler",
      "level": "INFO",
      "formatter": "fmt_line",
      "stream": "ext://sys.stdout"
    }
  },
  "loggers": {
    "svc_ERR": {
      "handlers": ["ConsoleSink", "ErrorSink"],
      "level": "ERROR",
      "propagate": false
    },
    "svc_INFO": {
      "handlers": ["ConsoleSink", "InfoSink"],
      "level": "INFO",
      "propagate": false
    }
  },
  "root": {
    "level": "DEBUG",
    "handlers": ["ConsoleSink", "InfoSink"]
  }
}

जब propagation वास्तविक boolean से बंद कर दिया जाता है, तो svc_ERR द्वारा संभाले गए रिकॉर्ड root तक नहीं पहुँचते, इसलिए वे app_info.log में नहीं लिखे जाते। इसी तरह, डुप्लीकेट कंसोल पंक्तियाँ भी गायब हो जाती हैं क्योंकि root अब वही रिकॉर्ड फिर से प्राप्त कर उसे दोबारा नहीं लिखता।

यह क्यों मायने रखता है

जब लॉग अनजाने में propagate होते हैं, तो INFO और ERROR फ़ाइलों के बीच की साफ़ अलगाव रेखा मिट जाती है। अलग-अलग लक्ष्यों वाले लॉग्स रखने का उद्देश्य कमजोर पड़ता है, और एक ही इवेंट कई हैंडलर्स द्वारा संसाधित होकर डुप्लीकेट आउटपुट पैदा करता है। यह सुनिश्चित करना कि propagate सचमुच बंद है, सटीक रूटिंग और शोर-भरी, भ्रामक लॉग स्ट्रीम्स के बीच का फ़र्क तय करता है।

मुख्य बातें

propagate को स्ट्रिंग की बजाय boolean false पर सेट करें। याद रखें, स्तरों की सीमा समावेशी होती है, इसलिए INFO पर मौजूद हैंडलर ERROR रिकॉर्ड भी स्वीकार करता है। यदि आप root लॉगर पर निर्भर हैं, तो ध्यान रखें कि इवेंट्स ऊपर तक जाएँगे, जब तक कि propagation स्पष्ट रूप से और सही तरीके से बंद न किया गया हो। इन बारीकियों को सही करें, और आपको बिना डुप्लीकेशन के, अलग-अलग और पूर्वानुमेय INFO और ERROR लॉग्स मिलेंगे।

यह लेख StackOverflow पर प्रश्न (लेखक: Veera V) और Grismar के उत्तर पर आधारित है।