java - जावा में एक अपवाद के लिए उपयोगी राज्य जानकारी को पास करने का एक अच्छा तरीका क्या है?




exception logging (8)

मैंने अपने प्रश्न के साथ शुरू में कुछ भ्रम को देखा मैं एक लॉगर को कॉन्फ़िगर करने के तरीके के बारे में नहीं पूछ रहा हूं और न ही ठीक से एक लॉगर का उपयोग कैसे करना है, बल्कि यह कि जिस जानकारी को अपवाद संदेश में वर्तमान लॉगिंग स्तर की तुलना में कम लॉगिंग स्तर पर लॉग किया गया है,

मैं लॉगिंग जानकारी के लिए जावा में दो पैटर्नों को देख रहा हूं जो एक डेवलपर के लिए उपयोगी हो सकता है जब कोई अपवाद उत्पन्न होता है।

निम्नलिखित पैटर्न बहुत आम हैं असल में, आपके पास सिर्फ आवश्यकता के अनुसार आपके लॉग-ऑन लॉग इन-लाइन की जानकारी है, इसलिए जब अपवाद उत्पन्न होता है तो आपके पास लॉग ट्रेस होता है।

try {
    String myValue = someObject.getValue();
    logger.debug("Value: {}", myValue);
    doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
    // consider this a RuntimeException wrapper class
    throw new UnhandledException(bthe);
}

उपरोक्त दृष्टिकोण की खातिर यह है कि यदि आपके उपयोगकर्ताओं को अपेक्षाकृत शांत लॉग की आवश्यकता होती है और इस बिंदु पर विश्वसनीयता की एक उच्च स्तरीय आवश्यकता होती है , जहां वे "डीबग मोड में फिर से कोशिश नहीं कर सकते हैं", अपवाद संदेश में अपर्याप्त डेटा शामिल है डेवलपर के लिए उपयोगी

अगले पैटर्न यह है कि मैंने देखा है जो इस समस्या को कम करने की कोशिश करता है, लेकिन बदसूरत लगता है:

String myValue = null;
try {
    myValue = someObject.getValue();
    doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
    String pattern = "An error occurred when setting value. [value={}]";
    // note that the format method below doesn't barf on nulls
    String detail = MessageFormatter.format(pattern, myValue);
    // consider this a RuntimeException wrapper class
    throw new UnhandledException(detail, bthe);
}

इसके बाद के संस्करण पैटर्न कुछ हद तक इस समस्या को हल करने लगता है, हालांकि मुझे यकीन नहीं है कि मैं कोशिश ब्लॉक के दायरे के बाहर इतने सारे चर घोषित करना चाहता हूं। विशेष रूप से, जब मुझे बहुत जटिल राज्यों से निपटना होगा।

मैंने देखा है कि एकमात्र अन्य तरीका कुंजी मान जोड़े को स्टोर करने के लिए मानचित्र का उपयोग कर रहा है, जो तब अपवाद संदेश में फेंक दिए जाते हैं। मुझे यकीन नहीं है कि मुझे यह दृष्टिकोण पसंद है क्योंकि यह कोड ब्लोट बनाने में प्रतीत होता है

क्या वहाँ कुछ जावा वोडु है जो मुझे याद आ रही है? आप अपवाद राज्य की जानकारी कैसे प्रबंधित करते हैं?


आप जिस प्रकार की डीबग की आवश्यकता है, उस प्रकार के लिए, आप हमेशा वैल्यू लॉग इन क्यों नहीं करते हैं और स्थानीय प्रयास / पकड़ के साथ इतना परेशान नहीं करते बस लॉग-इन कॉन्फिग फ़ाइल को अपने डीबग संदेशों को एक अलग लॉग पर इंगित करने के लिए उपयोग करें, या चेनसाओ का उपयोग करें ताकि आप दूरस्थ रूप से लॉग संदेशों का अनुसरण कर सकें। यदि सब कुछ विफल हो जाए तो आपको डिबग () / info () / warn () / त्रुटि () / घातक () में जोड़ने के लिए एक नया लॉग संदेश प्रकार की आवश्यकता है, इसलिए आपको अधिक नियंत्रण प्राप्त होगा कि कौन से संदेश किस प्रकार भेजे जाएंगे यह ऐसा मामला होगा जब log4j config फाइल में अपेंडर्स परिभाषित करना उन स्थानों की उच्च संख्या के कारण अव्यावहारिक होता है जहां इस प्रकार के डिबग लॉगिंग को डालने की आवश्यकता होती है।

जब हम इस विषय पर हैं, तो आपने मेरे पालतू जानवरों में से एक को छुआ है I कैच ब्लॉक में एक नया अपवाद बनाना एक कोड गंध है

Catch(MyDBException eDB)
{
    throw new UnhandledException("Something bad happened!", eDB);
}

लॉग में संदेश डालें और फिर अपवाद को फिर से खोलें। निर्माण अपवाद महँगा है और आसानी से उपयोगी डीबगिंग जानकारी छुपा सकता है।

सबसे पहले, अनुभवहीन सांकेतिक शब्दों में बदलनेवाला और जो लोग कट-एन-पेस्ट (या शुरू-चिह्न-बग, अंत-चिह्न-बग, कॉपी-बग, कॉपी-बग, कॉपी-बग) चाहते हैं, वे इसे आसानी से बदल सकते हैं:

Catch(MyDBException eDB)
{
    throw new UnhandledException("Something bad happened!");
}

अब आपने मूल स्टैकट्र्रेस खो दिया है। पहले मामले में, जब तक कि रैपिंग अपवाद लिपटे अपवाद ठीक से संभालता है, आप अभी भी मूल अपवाद का विवरण खो सकते हैं, स्टैकेट्र्रेस सबसे अधिक संभावना है।

अपग्रेड अपवादों की आवश्यकता हो सकती है लेकिन मुझे पता चला है कि इसे सामान्य रूप से और सामान्य रूप से नियंत्रित किया जाना चाहिए और परतों के बीच संवाद करने की रणनीति के रूप में, जैसे आपके व्यवसाय कोड और दृढता परत, जैसे:

Catch(SqlException eDB)
{
    throw new UnhandledAppException("Something bad happened!", eDB);
}

और इस मामले में, UnhandledAppException के लिए कैच ब्लॉक कॉल स्टैक के ऊपर है, जहां हम उपयोगकर्ता को यह संकेत दे सकते हैं कि उन्हें या तो उनकी कार्रवाई का पुन: प्रयास करने की आवश्यकता है, बग की रिपोर्ट करें, या जो कुछ भी

यह हमारे मुख्य कोड को ऐसा कुछ करने दो

catch(UnhandledAppException uae)
{
    \\notify user
    \\log exception
}
catch(Throwable tExcp)
{
    \\for all other unknown failures
    \\log exception

}
finally
{
    \\die gracefully
}

ऐसा करने से इसका मतलब था कि स्थानीय कोड तत्काल और पुनर्प्राप्त योग्य अपवादों को पकड़ सकता है, जहां डीबग लॉग किए जा सकते हैं और अपवाद फिर से नहीं किया जा सकता है। यह DivideByZero के लिए या शायद कुछ प्रकार के एक ParseException के लिए होगा

"फेंकता" खंडों के लिए, एक परत-आधारित अपवाद रणनीति होने के कारण प्रत्येक विधि के लिए सूचीबद्ध अपवाद प्रकारों की संख्या को सीमित करने में सक्षम होने का मतलब है।


आपके उदाहरण के अलावा, जो catch ब्लॉक के अंदर पहुंच योग्य होने के लिए catch ब्लॉक के बाहर स्थानीय क्षेत्रों को घोषित करता है, इसका प्रबंधन करने का एक बहुत ही आसान तरीका वर्ग की ओवरराइड toString विधि का उपयोग करके Exception में कक्षा की स्थिति को डंप करना है। दी, यह केवल Class में उपयोगी होता है जो राज्य को बनाए रखता है।

try {
   setMyValue(someObject.getValue());
   doSomething(getMyValue());
}
catch (BadThingsHappenException bthe) {
   // consider this a RuntimeException wrapper class
  throw new UnhandledException(toString(), bthe);
}

आपका toString() ओवरराइड होने की आवश्यकता होगी:

public String toString() {
   return super.toString() + "[myValue: " + getMyValue() +"]";
}

संपादित करें:

एक अन्य विचार:

आप ThreadLocal डिबग संदर्भ में राज्य को बनाए रख सकते हैं मान लीजिए कि आप MyDebugUtils नामक एक वर्ग MyDebugUtils जिसमें एक MyDebugUtils जिसमें मैप प्रति धागा होता है। आप इस ThreadLocal और रखरखाव के तरीकों तक स्थिर पहुंच के लिए अनुमति देते हैं (यानी, जब आपका डीबगिंग समाप्त होता है तो संदर्भ साफ़ करने के लिए)

इंटरफ़ेस यह हो सकता है:

public static void setValue(Object key, Object value)
public static void clearContext()
public static String getContextString() 

और हमारे उदाहरण में:

try {
   MyDebugUtils.setValue("someObeject.value", someObject.getValue());
   doSomething(someObject.getValue());
} catch (BadThingsHappenException bthe) {
   // consider this a RuntimeException wrapper class
  throw new UnhandledException(MyDebugUtils.getContextString(), bthe);
} finally {
  MyDebugUtils.clearContext(); 
}

ऐसी कुछ समस्याएं हो सकती हैं जिन्हें आप बाहर निकालना चाहते हैं, जैसे मामलों को संभालने के लिए, जहां आपकी doSomething विधि में एक try/catch/finally सेट होता है जो डीबग संदर्भ को साफ़ करता है इस प्रक्रिया में थ्रेड की तुलना में संदर्भ मानचित्र में बेहतर ग्रैन्युलैरिटी की अनुमति देकर इसका संचालन किया जा सकता है:

public static void setValue(Object contextID, Object key, Object value)
public static void clearContext(Object contextID)
public static String getContextString(Object contextID)

एक और अच्छा लॉगिंग एपीआई SLF4J है यह Log4J, जावा यूटिल लॉगिंग और जकार्ता कॉमन्स लॉगिंग के लिए लॉग एपीआई को भी अवरुद्ध करने के लिए कॉन्फ़िगर किया जा सकता है। और यह लॉग इन, लॉगबैक, जावा यूटिल लॉगिंग और एक या दो अन्य सहित विभिन्न लॉगिंग कार्यान्वयनों का उपयोग करने के लिए भी कॉन्फ़िगर किया जा सकता है। यह बहुत ही लचीलापन देता है यह Log4J के लेखक द्वारा इसके उत्तराधिकारी बनने के लिए विकसित किया गया था।

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

logger.debug("The new entry is " + entry + ".");
logger.debug("The new entry is {}.", entry);

एक दो तर्क संस्करण भी है:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

और दो से अधिक के लिए आप इस तरह ऑब्जेक्ट की एक सरणी में पारित कर सकते हैं:

logger.debug("Value {} was inserted between {} and {}.", 
             new Object[] {newVal, below, above});

यह एक अच्छा संक्षिप्त प्रारूप है जो अव्यवस्था को समाप्त करता है

उदाहरण स्रोत SLF4J अकसर किये गए सवाल से है

संपादित करें: आपके उदाहरण का एक संभावित रिफ़ैक्चरिंग है:

try {
    doSomething(someObject.getValue());
}
catch (BadThingsHappenException bthe) {
  throw new UnhandledException(
    MessageFormatter.format("An error occurred when setting value. [value={}]", 
                              someObject.getValue()), 
    bthe);
}

या यदि यह पैटर्न कुछ जगहों से अधिक होता है तो आप स्थिर तरीके का एक सेट लिख सकते हैं जो सामान्यता को कैप्चर करते हैं, जैसे कुछ:

try {
    doSomething(someObject.getValue());
}
catch (BadThingsHappenException bthe) {
    throwFormattedException(logger, bthe,
                            "An error occurred when setting value. [value={}]", 
                            someObject.getValue()));
}

और निश्चित रूप से विधि आपके लिए लॉगर पर फ़ॉर्मेटेड संदेश भी डाल देगी।


एक विकल्प जो कि कोई भी अभी तक उल्लेख नहीं किया है लगता है कि एक लॉगर का उपयोग करना जो कि स्मृति बफर में लॉग करता है, और केवल कुछ परिस्थितियों में वास्तविक लॉग लक्ष्य में जानकारी को धक्का देता है (उदाहरण के लिए, एक त्रुटि स्तर संदेश लॉग होता है)।

यदि आप JDK 1.4 लॉगिंग सुविधा का उपयोग कर रहे हैं, तो मेमोरीहैंडलर ऐसा ही करता है। मुझे यकीन नहीं है कि यदि आपके द्वारा उपयोग की जा रही लॉगिंग सिस्टम ऐसा करता है, लेकिन मुझे लगता है कि आपको अपने खुद के एपेन्डर / हेन्डलर को लागू करने में सक्षम होना चाहिए / जो भी कुछ इसी तरह से करता है।

इसके अलावा, मैं यह भी कहना चाहता हूं कि आपके मूल उदाहरण में, यदि आपकी चिंता चर की गुंजाइश है, तो आप हमेशा अपने चर के दायरे को कम करने के लिए एक ब्लॉक परिभाषित कर सकते हैं:

{
    String myValue = null;
    try {
        myValue = someObject.getValue();
        doSomething(myValue);
    }
    catch (BadThingsHappenException bthe) {
        String pattern = "An error occurred when setting value. [value={}]";
        // note that the format method below doesn't barf on nulls
        String detail = MessageFormatter.format(pattern, myValue);
        // consider this a RuntimeException wrapper class
        throw new UnhandledException(detail, bthe);
    }
}

यदि आप किसी तरह त्रुटि संदेश का विवरण संसाधित करना चाहते हैं, तो आप निम्न कर सकते हैं:

  • संदेश के रूप में एक XML पाठ का प्रयोग करें, ताकि आपको एक संरचित तरीके से प्राप्त किया जा सके:

    throw new UnhandledException(String.format(
        "<e><m>Unexpected things</m><value>%s</value></e>", value), bthe);
  • नामित संपत्तियों में चर जानकारी को कैप्चर करने के लिए अपना स्वयं का (और प्रत्येक मामले के लिए) अपवाद प्रकारों का उपयोग करें:

    throw new UnhandledValueException("Unexpected value things", value, bthe);

अन्यथा आप इसे कूड़े संदेश में शामिल कर सकते हैं, जैसा कि अन्य लोगों द्वारा सुझाया गया है


शायद मुझे कुछ याद आ रहा है, लेकिन अगर उपयोगकर्ताओं को अपेक्षाकृत शांत लॉग फ़ाइल की आवश्यकता होती है, तो आप अपने डीबग लॉग को एक अलग स्थान पर जाने के लिए क्यों नहीं कॉन्फ़िगर करते हैं?

यदि वह अपर्याप्त है, तो राम में डीबग लॉग की एक निश्चित राशि पर कब्जा। उदाहरण, पिछले 500 प्रविष्टियां तब, जब कुछ बदसूरत होता है, समस्या रिपोर्ट के साथ डीबग लॉग को डंप करें आप अपने लॉगिंग ढांचे का उल्लेख नहीं करते, लेकिन Log4J में ऐसा करना बहुत आसान होगा

इससे भी बेहतर, मान लें कि आपके पास उपयोगकर्ता की अनुमति है, बस लॉगिंग के बजाय एक स्वचालित त्रुटि रिपोर्ट भेजें। मैंने हाल ही में कुछ लोगों को कठोर-से-मिलते-जुलते बग को चलाने में मदद की और स्वचालित रूप से रिपोर्टिंग के लिए त्रुटि दर्ज की। हमें बग रिपोर्ट की संख्या 50x मिली, जिससे समस्या को खोजने में बहुत आसान हो गया।


हम कुछ खास कन्स्ट्रक्टर, कुछ स्थिरांक और एक संसाधन बंडल के साथ हमारे सबसे महत्वपूर्ण एप्लीकेशन विशिष्ट रनटाइम अपवाद क्लास बनाने की कोशिश करते हैं।

उदाहरण स्निपेट:

 public class MyException extends RuntimeException
 {
    private static final long serialVersionUID = 5224152764776895846L;

    private static final ResourceBundle MESSAGES;
    static
    {
        MESSAGES = ResourceBundle.getBundle("....MyExceptionMessages");
    }

    public static final String NO_CODE = "unknown";
    public static final String PROBLEMCODEONE = "problemCodeOne";
    public static final String PROBLEMCODETWO = "problemCodeTwo";
    // ... some more self-descriptive problem code constants

    private String errorCode = NO_CODE;
    private Object[] parameters = null;

    // Define some constructors

    public MyException(String errorCode)
    {
        super();
        this.errorCode = errorCode;
    }

    public MyException(String errorCode, Object[] parameters)   
    {
        this.errorCode = errorCode;
        this.parameters = parameters;
    }

    public MyException(String errorCode, Throwable cause)
    {
        super(cause);
        this.errorCode = errorCode;
    }

    public MyException(String errorCode, Object[] parameters, Throwable cause)
    {
        super(cause);
        this.errorCode = errorCode;
        this.parameters = parameters;
    }   

    @Override
    public String getLocalizedMessage()
    {
        if (NO_CODE.equals(errorCode))
        {
            return super.getLocalizedMessage();
        }

        String msg = MESSAGES.getString(errorCode); 
        if(parameters == null)
        {
            return msg;
        }
        return MessageFormat.format(msg, parameters);
    }
 }

गुण फ़ाइल में हम अपवाद वर्णन निर्दिष्ट करते हैं, जैसे:

 problemCodeOne=Simple exception message
 problemCodeTwo=Parameterized exception message for {0} value

इस दृष्टिकोण का उपयोग करना

  • हम काफी पठनीय और समझा जा सकने वाले फेंक के नियमों का उपयोग कर सकते हैं ( throw new MyException(MyException.PROBLEMCODETWO, new Object[] {parameter}, bthe)
  • अपवाद संदेश "केंद्रीकृत" हैं, आसानी से बनाए रख सकते हैं और "अंतर्राष्ट्रीयकृत"

संपादित करें: getMessage सुझाव के रूप में getMessage करने के लिए getLocalizedMessage

EDIT2: उल्लेख करना भूल गया: यह दृष्टिकोण लोकल को "पर-उड़" को बदलने का समर्थन नहीं करता है, लेकिन यह जानबूझकर है (यदि आपको इसकी आवश्यकता होती है तो इसे लागू किया जा सकता है)।


Java.util.logging से स्मृतिहैंडलर वर्ग पर एक नज़र डालें। यह आपके लॉग के बीच एक बफर के रूप में कार्य करता है। $ स्तर () इनवोकेशन और वास्तविक आउटपुट, और बफर सामग्री को आउटपुट में तब ही पारित कर देगा जब कुछ स्थिति पूरी हो।

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

मुझे लगता है कि अन्य प्रवेश चौखटे के लिए समान लागूकरण हैं।

संपादित करें: इस दृष्टिकोण के साथ एक संभव मुद्दा सभी डीबग संदेशों को सृजित करने पर खो गया एक प्रदर्शन है (@ डीजेना टिप्पणी देखें)। इस वजह से बफर विन्यास में प्रवेश करने के स्तर को बनाने के लिए यह एक अच्छा विचार हो सकता है- उत्पादन में यह जानकारी या उससे अधिक होना चाहिए, और तभी यदि आप सक्रिय रूप से एक समस्या को शिकार कर रहे हैं तो इसे DEBUG में बदल दिया जा सकता है।







error-handling