कोटलिन में लॉगिंग का मुहावरेदार तरीका


164

कोटलिन के पास स्थिर क्षेत्रों की वैसी ही धारणा नहीं है जैसी कि जावा में होती है। जावा में, लॉगिंग करने का आम तौर पर स्वीकृत तरीका है:

public class Foo {
    private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}

सवाल यह है कि कोटलिन में लॉगिंग प्रदर्शन का मुहावरेदार तरीका क्या है?


1
इसे उत्तर के कारण के रूप में पोस्ट न करना यह जावा के रास्ते से बहुत दूर है, लेकिन मैंने लॉगिंग के लिए एनी पर एक विस्तार फ़ंक्शन लिखने पर विचार किया है। आपको लॉगर्स को कैश करने की आवश्यकता है, लेकिन मुझे लगता है कि यह करने का एक अच्छा तरीका होगा।
mhlz

1
@mhlz उस विस्तार समारोह को सांख्यिकीय रूप से हल नहीं किया जाएगा? जैसा कि, यह सभी वस्तुओं पर लागू नहीं होगा, केवल उन प्रकारों के लिए Any(इस प्रकार एक डाली की आवश्यकता है)?
जीर

1
@mhlz एक विस्तार समारोह का कोई मतलब नहीं है क्योंकि इसमें एक लकड़हारा रखने के लिए राज्य नहीं होगा। यह एक लकड़हारा लौटाने के लिए एक विस्तार हो सकता है, लेकिन सिस्टम में हर ज्ञात वर्ग पर ऐसा क्यों है? बाद में IDE में टेढ़ा शोर बनने के लिए किसी भी प्रवृत्ति पर विस्तार करना। @ किसी भी के सभी वंशजों पर विस्तार लागू होगा, फिर भी this.javaClassप्रत्येक के लिए सही लौटाएगा । लेकिन मैं इसे एक समाधान के रूप में अनुशंसित नहीं कर रहा हूं।
जैसन मिनार्ड

जवाबों:


250

अधिकांश परिपक्व कोटलिन कोड में, आपको नीचे इनमें से एक पैटर्न मिलेगा। प्रॉपर्टी डेलीगेट्स का उपयोग करने वाला दृष्टिकोण सबसे छोटे कोड का उत्पादन करने के लिए कोटलिन की शक्ति का लाभ उठाता है।

नोट: यहाँ कोड है, java.util.Loggingलेकिन समान सिद्धांत किसी भी लॉगिंग लाइब्रेरी पर लागू होता है

स्टेटिक-लाइक (सामान्य, प्रश्न में आपके जावा कोड के बराबर)

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

class MyClass {
    companion object {
        val LOG = Logger.getLogger(MyClass::class.java.name) 
    }

    fun foo() {
        LOG.warning("Hello from MyClass")
    }
}  

आउटपुट बनाना:

Dec 26, 2015 11:28:32 AM org.stackoverflow.kotlin.test.MyClassfoo जानकारी: MyClass से नमस्कार

साथी के बारे में अधिक यहाँ वस्तुओं: कम्पेनियन वस्तुओं ... यह भी ध्यान रखें नमूने में है कि इसके बाद के संस्करण MyClass::class.javaप्रकार के उदाहरण हो जाता है Class<MyClass>लकड़हारा के लिए है, जबकि this.javaClassप्रकार के उदाहरण मिलेगा Class<MyClass.Companion>

एक कक्षा की प्रति घटना (सामान्य)

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

class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

  fun foo() {
        LOG.warning("Hello from MyClass")
  }
} 

आउटपुट बनाना:

Dec 26, 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo जानकारी: नमस्कार MyClass के लिए

आप उदाहरण और प्रति वर्ग भिन्नता दोनों का परीक्षण कर सकते हैं और यह देख सकते हैं कि अधिकांश ऐप्स के लिए यथार्थवादी अंतर है या नहीं।

संपत्ति के प्रतिनिधि (सामान्य, सबसे सुरुचिपूर्ण)

एक अन्य दृष्टिकोण, जिसे @Jire द्वारा दूसरे उत्तर में सुझाया गया है, एक संपत्ति प्रतिनिधि बनाना है, जिसका उपयोग आप तब किसी अन्य वर्ग में तर्क को समान रूप से करने के लिए कर सकते हैं जो आप चाहते हैं। ऐसा करने का एक सरल तरीका है क्योंकि कोटलिन Lazyपहले से ही एक प्रतिनिधि प्रदान करता है , हम इसे केवल एक फ़ंक्शन में लपेट सकते हैं। यहाँ एक चाल यह है कि यदि हम वर्तमान में प्रतिनिधि का उपयोग कर रहे वर्ग के प्रकार को जानना चाहते हैं, तो हम इसे किसी भी वर्ग पर एक विस्तार समारोह बनाते हैं:

fun <R : Any> R.logger(): Lazy<Logger> {
    return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) }
}
// see code for unwrapCompanionClass() below in "Putting it all Together section"

यह कोड यह भी सुनिश्चित करता है कि यदि आप इसका उपयोग किसी कंपेनियन ऑब्जेक्ट में करते हैं, तो लकड़हारा का नाम वैसा ही होगा जैसा कि आपने इसे कक्षा में उपयोग किया था। अब आप बस:

class Something {
    val LOG by logger()

    fun foo() {
        LOG.info("Hello from Something")
    }
}

प्रति वर्ग उदाहरण के लिए, या यदि आप चाहते हैं कि यह प्रति वर्ग एक उदाहरण के साथ अधिक स्थिर हो:

class SomethingElse {
    companion object {
        val LOG by logger()

    }

    fun foo() {
        LOG.info("Hello from SomethingElse")
    }
}

और foo()इन दोनों वर्गों पर कॉल करने से आपका आउटपुट होगा:

Dec 26, 2015 11:30:55 AM org.stackoverflow.kotlin.test.Something फू जानकारी: कुछ से नमस्ते

Dec 26, 2015 11:30:55 AM org.stackoverflow.kotlin.test.SomethingElse foo जानकारी: हेलो फ्रॉम समथिंग एल्स

एक्सटेंशन फ़ंक्शंस (किसी भी नामस्थान के "प्रदूषण" के कारण इस मामले में असामान्य)

कोटलिन के पास कुछ छुपी हुई तरकीबें हैं जो आपको इस कोड को कुछ और छोटा बना देती हैं। आप कक्षाओं पर एक्सटेंशन फ़ंक्शन बना सकते हैं और इसलिए उन्हें अतिरिक्त कार्यक्षमता दे सकते हैं। ऊपर टिप्पणियों में एक सुझाव Anyएक लकड़हारा समारोह के साथ विस्तार करना था । यह कभी भी शोर पैदा कर सकता है जब कोई किसी भी वर्ग में अपने आईडीई में कोड-पूरा करने का उपयोग करता है। लेकिन Anyकुछ अन्य मार्कर इंटरफ़ेस को विस्तारित करने के लिए एक गुप्त लाभ है : आप इसका मतलब यह कर सकते हैं कि आप अपनी खुद की कक्षा का विस्तार कर रहे हैं और इसलिए उस वर्ग का पता लगाएं जो आप भीतर हैं। है ना? कम भ्रमित होने के लिए, यहाँ कोड है:

// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
     return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}

अब एक वर्ग (या साथी वस्तु) के भीतर, मैं इस विस्तार को अपनी कक्षा में कह सकता हूं:

class SomethingDifferent {
    val LOG = logger()

    fun foo() {
        LOG.info("Hello from SomethingDifferent")
    }
}

उत्पादन उत्पादन:

Dec 26, 2015 11:29:12 AM org.stackoverflow.kotlin.test.SomethingDifferent foo जानकारी: HelloDifDifferent से

मूल रूप से, कोड को एक्सटेंशन के लिए कॉल के रूप में देखा जाता है Something.logger()। समस्या यह है कि निम्नलिखित अन्य वर्गों पर "प्रदूषण" बनाने में भी सच हो सकता है:

val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()

मार्कर इंटरफ़ेस पर एक्सटेंशन फ़ंक्शंस (निश्चित नहीं कि "लक्षण" के लिए कितना सामान्य, लेकिन सामान्य मॉडल है)

एक्सटेंशन क्लीनर का उपयोग करने और "प्रदूषण" को कम करने के लिए, आप विस्तार करने के लिए एक मार्कर इंटरफ़ेस का उपयोग कर सकते हैं:

interface Loggable {} 

fun Loggable.logger(): Logger {
     return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}    

या यहां तक ​​कि डिफ़ॉल्ट कार्यान्वयन के साथ इंटरफ़ेस का तरीका भी बनाएं:

interface Loggable {
    public fun logger(): Logger {
        return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
    }
}

और अपनी कक्षा में इन विविधताओं का उपयोग करें:

class MarkedClass: Loggable {
    val LOG = logger()
}

उत्पादन उत्पादन:

Dec 26, 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo जानकारी: MarkedClass से नमस्कार

यदि आप लकड़हारे को रखने के लिए एक समान क्षेत्र के निर्माण के लिए बाध्य करना चाहते हैं, तो इस इंटरफ़ेस का उपयोग करते समय आपको आसानी से कार्यान्वयनकर्ता की आवश्यकता हो सकती है जैसे कि एक क्षेत्र LOG:

interface Loggable {
    val LOG: Logger  // abstract required field

    public fun logger(): Logger {
        return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
    }
}

अब इंटरफ़ेस के कार्यान्वयनकर्ता को इस तरह दिखना चाहिए:

class MarkedClass: Loggable {
    override val LOG: Logger = logger()
}

बेशक, एक अमूर्त आधार वर्ग वही कर सकता है, जिसमें इंटरफ़ेस और अमूर्त वर्ग दोनों का विकल्प होता है जो उस इंटरफ़ेस को लागू करता है जो लचीलापन और एकरूपता की अनुमति देता है:

abstract class WithLogging: Loggable {
    override val LOG: Logger = logger()
}

// using the logging from the base class
class MyClass1: WithLogging() {
    // ... already has logging!
}

// providing own logging compatible with marker interface
class MyClass2: ImportantBaseClass(), Loggable {
    // ... has logging that we can understand, but doesn't change my hierarchy
    override val LOG: Logger = logger()
}

// providing logging from the base class via a companion object so our class hierarchy is not affected
class MyClass3: ImportantBaseClass() {
    companion object : WithLogging() {
       // we have the LOG property now!
    }
}

यह सब एक साथ रखना (एक छोटा सहायक पुस्तकालय)

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

// Return logger for Java class, if companion object fix the name
fun <T: Any> logger(forClass: Class<T>): Logger {
    return Logger.getLogger(unwrapCompanionClass(forClass).name)
}

// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> { 
   return ofClass.enclosingClass?.takeIf { 
      ofClass.enclosingClass.kotlin.companionObject?.java == ofClass 
   } ?: ofClass 
}

// unwrap companion class to enclosing class given a Kotlin Class
fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> {
   return unwrapCompanionClass(ofClass.java).kotlin
}

// Return logger for Kotlin class
fun <T: Any> logger(forClass: KClass<T>): Logger {
    return logger(forClass.java)
}

// return logger from extended class (or the enclosing class)
fun <T: Any> T.logger(): Logger {
    return logger(this.javaClass)
}

// return a lazy logger property delegate for enclosing class
fun <R : Any> R.lazyLogger(): Lazy<Logger> {
    return lazy { logger(this.javaClass) }
}

// return a logger property delegate for enclosing class
fun <R : Any> R.injectLogger(): Lazy<Logger> {
    return lazyOf(logger(this.javaClass))
}

// marker interface and related extension (remove extension for Any.logger() in favour of this)
interface Loggable {}
fun Loggable.logger(): Logger = logger(this.javaClass)

// abstract base class to provide logging, intended for companion objects more than classes but works for either
abstract class WithLogging: Loggable {
    val LOG = logger()
}

इनमें से जो भी आप रखना चाहते हैं, चुनें और यहाँ उपयोग में सभी विकल्प हैं:

class MixedBagOfTricks {
    companion object {
        val LOG1 by lazyLogger()          // lazy delegate, 1 instance per class
        val LOG2 by injectLogger()        // immediate, 1 instance per class
        val LOG3 = logger()               // immediate, 1 instance per class
        val LOG4 = logger(this.javaClass) // immediate, 1 instance per class
    }

    val LOG5 by lazyLogger()              // lazy delegate, 1 per instance of class
    val LOG6 by injectLogger()            // immediate, 1 per instance of class
    val LOG7 = logger()                   // immediate, 1 per instance of class
    val LOG8 = logger(this.javaClass)     // immediate, 1 instance per class
}

val LOG9 = logger(MixedBagOfTricks::class)  // top level variable in package

// or alternative for marker interface in class
class MixedBagOfTricks : Loggable {
    val LOG10 = logger()
}

// or alternative for marker interface in companion object of class
class MixedBagOfTricks {
    companion object : Loggable {
        val LOG11 = logger()
    }
}

// or alternative for abstract base class for companion object of class
class MixedBagOfTricks {
    companion object: WithLogging() {} // instance 12

    fun foo() {
       LOG.info("Hello from MixedBagOfTricks")
    }
}

// or alternative for abstract base class for our actual class
class MixedBagOfTricks : WithLogging() { // instance 13
    fun foo() {
       LOG.info("Hello from MixedBagOfTricks")
    }
}

इस नमूने में बनाए गए लकड़हारे के सभी 13 उदाहरण एक ही लकड़हारा नाम और उत्पादन का उत्पादन करेंगे:

Dec 26, 2015 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo जानकारी: HelloBagOfTricks से नमस्कार

नोट:unwrapCompanionClass() विधि दर्शाता है कि हम साथी वस्तु बल्कि संलग्नित क्लास के नाम पर एक लकड़हारा उत्पन्न नहीं है। साथी ऑब्जेक्ट वाली कक्षा को खोजने के लिए यह वर्तमान अनुशंसित तरीका है। नाम का उपयोग करके " $ साथी " स्ट्रिपिंग removeSuffix()काम नहीं करता है क्योंकि साथी वस्तुओं को कस्टम नाम दिया जा सकता है।


कुछ निर्भरता इंजेक्शन फ्रेमवर्क प्रतिनिधियों का उपयोग करते हैं जैसे आप यहां एक अन्य उत्तर में देखते हैं। वे `वल लॉग: लॉगर बाय इंजेक्टलॉगर ()` की तरह दिखते हैं और लॉगिंग सिस्टम को इंजेक्ट करने के लिए और अज्ञात कोड का उपयोग करने की अनुमति देते हैं। (यह दिखाने के लिए मेरा इंजेक्शन ढांचा github.com/kohesive/injekt पर है )
जैसन मिनार्ड

10
व्यापक उत्तर के लिए धन्यवाद। बहुत सूचनाप्रद। मुझे विशेष रूप से प्रॉपर्टी डेलिगेट्स (आम, सबसे सुरुचिपूर्ण) कार्यान्वयन पसंद है।
mchlstckl

6
मुझे लगता है कि कोटलिन सिंटैक्स में एक बदलाव था। और ofClass.enclosingClass.kotlin.objectInstance?.javaClassofClass.enclosingClass.kotlin.companionObject?.java
अपराह्न के

1
आह, कोई बात नहीं, जैसा कि यहां बताया गया है kotlinlang.org/docs/reference/reflection.html परिलक्षित जार को stdlib से अलग से भेज दिया जाता है, ग्रेडेल के लिए हमें इसकी आवश्यकता होती है:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
'प्रॉपर्टी डेलीगेट्स' और 'एक्सटेंशन फ़ंक्शंस' बनाने के लिए कोड रिटर्न प्रकार को छोड़कर समान दिखाई देते हैं। प्रॉपर्टी डेलिगेट ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) के लिए कोड का नमूना एक विस्तार समारोह बनाता है, जो "".logger()अब एक बात है, क्या यह इस तरह से माना जाता है?
माइक रायलैंडर

32

कोटलिन-लॉगिंग लाइब्रेरी पर एक नज़र डालें ।
यह इस तरह लॉगिंग की अनुमति देता है:

private val logger = KotlinLogging.logger {}

class Foo {
  logger.info{"wohoooo $wohoooo"}
}

या उस तरह:

class FooWithLogging {
  companion object: KLogging()
  fun bar() {
    logger.info{"wohoooo $wohoooo"}
  }
}

मैं भी के साथ उसकी तुलना एक ब्लॉग पोस्ट में लिखा था AnkoLogger: बनाम kotlin-लॉगिंग AnkoLogger: Kotlin और एंड्रॉयड में लॉगिंग

अस्वीकरण: मैं उस पुस्तकालय का अनुरक्षक हूं।

संपादित करें: kotlin- लॉगिंग के पास अब मल्टीप्लेट रिकॉर्डर सहायता है: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


मेरा सुझाव है कि हो सकता है आप को दिखाने के लिए अपने जवाब संपादित उत्पादन की logger.info(), कॉल के रूप में जेसन उसके स्वीकार किए जाते हैं जवाब में किया था।
पाउलो मर्सन

7

लॉगिंग कार्यान्वयन के एक अच्छे उदाहरण के रूप में, मैं Anko का उल्लेख करना चाहूंगा जो एक विशेष इंटरफ़ेस का उपयोग करता है AnkoLoggerजिसे एक वर्ग की आवश्यकता होती है जिसे लॉगिंग को लागू करना चाहिए। इंटरफ़ेस के अंदर कोड है जो कक्षा के लिए एक लॉगिंग टैग बनाता है। लॉगिंग तब विस्तार कार्यों के माध्यम से किया जाता है जिसे उपसर्गों या यहां तक ​​कि लकड़हारा आवृत्ति निर्माण के बिना इंटरस कार्यान्वयन के अंदर बुलाया जा सकता है।

मुझे नहीं लगता कि यह मुहावरेदार है , लेकिन यह एक अच्छा तरीका लगता है क्योंकि इसमें न्यूनतम कोड की आवश्यकता होती है, बस इंटरफ़ेस को एक वर्ग घोषणा में जोड़ा जाता है, और आपको विभिन्न वर्गों के लिए अलग-अलग टैग के साथ लॉगिंग मिलती है।


नीचे दिया गया कोड मूल रूप से AnkoLogger है , जो Android-agnostic उपयोग के लिए सरलीकृत और पुनर्लेखन है।

सबसे पहले, एक इंटरफ़ेस है जो एक मार्कर इंटरफ़ेस की तरह व्यवहार करता है:

interface MyLogger {
    val tag: String get() = javaClass.simpleName
}

यह इसके कार्यान्वयन MyLoggerको उनके कोड के अंदर एक्सटेंशन फ़ंक्शन का उपयोग करने देता है, बस उन्हें कॉल करता है this। और इसमें लॉगिंग टैग भी है।

अगला, विभिन्न लॉगिंग विधियों के लिए एक सामान्य प्रवेश बिंदु है:

private inline fun log(logger: MyLogger,
                       message: Any?,
                       throwable: Throwable?,
                       level: Int,
                       handler: (String, String) -> Unit,
                       throwableHandler: (String, String, Throwable) -> Unit
) {
    val tag = logger.tag
    if (isLoggingEnabled(tag, level)) {
        val messageString = message?.toString() ?: "null"
        if (throwable != null)
            throwableHandler(tag, messageString, throwable)
        else
            handler(tag, messageString)
    }
}

इसे लॉगिंग विधियों द्वारा बुलाया जाएगा। इसे MyLoggerकार्यान्वयन से एक टैग मिलता है, लॉगिंग सेटिंग्स की जांच करता है और फिर दो संचालकों में से एक को कॉल करता है, एक Throwableतर्क के साथ और एक बिना।

फिर आप इस तरह से कई लॉगिंग विधियों को परिभाषित कर सकते हैं:

fun MyLogger.info(message: Any?, throwable: Throwable? = null) =
        log(this, message, throwable, LoggingLevels.INFO,
            { tag, message -> println("INFO: $tag # $message") },
            { tag, message, thr -> 
                println("INFO: $tag # $message # $throwable");
                thr.printStackTrace()
            })

ये केवल एक संदेश को लॉग करने और Throwableसाथ ही लॉग इन करने के लिए परिभाषित किए गए हैं , यह वैकल्पिक throwableपैरामीटर के साथ किया जाता है ।

जो फ़ंक्शन पास किए गए हैं handlerऔर throwableHandlerविभिन्न लॉगिंग विधियों के लिए भिन्न हो सकते हैं, उदाहरण के लिए, वे लॉग फाइल करने के लिए लिख सकते हैं या इसे कहीं अपलोड कर सकते हैं। isLoggingEnabledतथाLoggingLevels संक्षिप्तता के लिए छोड़ दिया जाता है, लेकिन उनका उपयोग करने से और भी अधिक लचीलापन मिलता है।


यह निम्नलिखित उपयोग के लिए अनुमति देता है:

class MyClass : MyLogger {
    fun myFun() {
        info("Info message")
    }
}

एक छोटी सी खामी है: पैकेज स्तर के कार्यों में लॉगिंग के लिए एक लकड़हारा वस्तु की आवश्यकता होगी:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}

यह उत्तर एंड्रॉइड विशिष्ट है, और इस प्रश्न का उल्लेख नहीं है और न ही एक एंड्रॉइड टैग है।
जैसन मिनार्ड

@JaysonMinard यह क्यों है? यह दृष्टिकोण सामान्य उद्देश्य है, उदाहरण के लिए, हर वर्ग के लिए एक अद्वितीय लॉगिंग टैग होना गैर-एंड्रॉइड परियोजनाओं में भी उपयोगी है।
हॉटकी

1
यह स्पष्ट नहीं है कि आप "कुछ ऐसा ही लागू करते हैं जैसा कि Anko ने किया था" और इसके बजाय "Anko का उपयोग करें" जैसा लगता है ... जिसके लिए Android की आवश्यकता होती है, जिसे Anko कहा जाता है। जिसका एक इंटरफ़ेस है जिसमें एक्सटेंशन फ़ंक्शन हैं जो android.util.Logलॉगिंग करने के लिए कॉल करते हैं। आपका इरादा क्या था? Anko का उपयोग करें? उदाहरण के रूप में Anko का उपयोग करते समय कुछ इसी तरह का निर्माण करना (यह बेहतर है यदि आप सिर्फ सुझाए गए कोड इनलाइन को डालते हैं और "गैर-एंड्रॉइड के लिए इसे पोर्ट करें, यहां लिंक है" कहने के बजाय गैर-एंड्रॉइड के लिए इसे ठीक करें। इसके बजाय आप नमूना कोड जोड़ते हैं। अनको को बुलाना)
जैसन मिनार्ड

1
@JaysonMinard, आपकी टिप्पणियों के लिए धन्यवाद, मैंने पोस्ट को फिर से लिखा है ताकि यह अब Anko संदर्भों के बजाय दृष्टिकोण की व्याख्या करे।
हॉटकी

6

KISS: जावा टीमें माइग्रेट Kotlin करने के लिए

यदि आपको लकड़हारा (बस जावा की तरह) की प्रत्येक तात्कालिकता पर कक्षा का नाम प्रदान करने में कोई आपत्ति नहीं है, तो आप इसे अपनी परियोजना में कहीं न कहीं एक शीर्ष-स्तरीय फ़ंक्शन के रूप में परिभाषित करके सरल रख सकते हैं:

import org.slf4j.LoggerFactory

inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java)

यह एक कोटलिन रिवाइज्ड टाइप पैरामीटर का उपयोग करता है ।

अब, आप इसका उपयोग इस प्रकार कर सकते हैं:

class SomeClass {
  // or within a companion object for one-instance-per-class
  val log = logger<SomeClass>()
  ...
}

यह दृष्टिकोण सुपर-सरल है और जावा समकक्ष के करीब है, लेकिन बस कुछ कृत्रिम चीनी जोड़ता है।

अगला चरण: एक्सटेंशन या प्रतिनिधि

मैं व्यक्तिगत रूप से एक कदम आगे जाने और एक्सटेंशन या प्रतिनिधियों के दृष्टिकोण का उपयोग करना पसंद करता हूं। यह @ JaysonMinard के जवाब में अच्छी तरह से संक्षेप में प्रस्तुत किया गया है, लेकिन यहाँ लॉग इन किया गया है, लॉग 4j2 API ( UPDATE : "के साथ" डेलिगेट "दृष्टिकोण के लिए DR; इस कोड को मैन्युअल रूप से लिखने की आवश्यकता नहीं है, क्योंकि यह आधिकारिक मॉड्यूल के रूप में जारी किया गया है। log4j2 परियोजना, नीचे देखें)। Log4j2 के बाद से, slf4j के विपरीत, लॉगिंग का समर्थन करता है Supplier, मैंने इन विधियों को सरल बनाने के लिए एक प्रतिनिधि भी जोड़ा है।

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.util.Supplier
import kotlin.reflect.companionObject

/**
 * An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the
 * method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier`
 * to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level
 * is not enabled.
 */
class FunctionalLogger(val log: Logger): Logger by log {
  inline fun debug(crossinline supplier: () -> String) {
    log.debug(Supplier { supplier.invoke() })
  }

  inline fun debug(t: Throwable, crossinline supplier: () -> String) {
    log.debug(Supplier { supplier.invoke() }, t)
  }

  inline fun info(crossinline supplier: () -> String) {
    log.info(Supplier { supplier.invoke() })
  }

  inline fun info(t: Throwable, crossinline supplier: () -> String) {
    log.info(Supplier { supplier.invoke() }, t)
  }

  inline fun warn(crossinline supplier: () -> String) {
    log.warn(Supplier { supplier.invoke() })
  }

  inline fun warn(t: Throwable, crossinline supplier: () -> String) {
    log.warn(Supplier { supplier.invoke() }, t)
  }

  inline fun error(crossinline supplier: () -> String) {
    log.error(Supplier { supplier.invoke() })
  }

  inline fun error(t: Throwable, crossinline supplier: () -> String) {
    log.error(Supplier { supplier.invoke() }, t)
  }
}

/**
 * A delegate-based lazy logger instantiation. Use: `val log by logger()`.
 */
@Suppress("unused")
inline fun <reified T : Any> T.logger(): Lazy<FunctionalLogger> =
  lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }

// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
  return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
    ofClass.enclosingClass
  } else {
    ofClass
  }
}

Log4j2 कोटलिन लॉगिंग एपीआई

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

उपयोग मूल रूप से ऊपर वर्णित के रूप में है, लेकिन मॉड्यूल इंटरफ़ेस-आधारित लॉगर एक्सेस दोनों का समर्थन करता है, उपयोग के लिए एक loggerएक्सटेंशन फ़ंक्शन Anyजहां thisपरिभाषित किया गया है, और उपयोग के लिए एक नामित लॉगर फ़ंक्शन जहां कोई thisपरिभाषित नहीं है (जैसे शीर्ष-स्तरीय फ़ंक्शन)।


1
यदि मैं सही हूं, तो आप विधि समाधान को T.logger ()
IPat

1
@ टिप यूप, पहला समाधान जानबूझकर ऐसा नहीं करता है कि "जावा रास्ते" के करीब बने रहें। उत्तर का दूसरा भाग एक्सटेंशन केस को कवर करता है T.logger()- कोड नमूने के नीचे देखें।
रमन

5

Anko

आप इसे करने के लिए Ankoपुस्तकालय का उपयोग कर सकते हैं । आपके पास नीचे जैसा कोड होगा:

class MyActivity : Activity(), AnkoLogger {
    private fun someMethod() {
        info("This is my first app and it's awesome")
        debug(1234) 
        warn("Warning")
    }
}

kotlin-लॉगिंग

कोटलिन-लॉगिंग ( जीथब प्रोजेक्ट - कोटलिन-लॉगिंग ) लाइब्रेरी आपको नीचे दिए गए लॉगिंग कोड लिखने की अनुमति देता है:

class FooWithLogging {
  companion object: KLogging()
  fun bar() {
    logger.info{"Item $item"}
  }
}

StaticLog

या आप कोटलिन पुस्तकालय में लिखे गए इस छोटे से नाम का भी उपयोग कर सकते हैं, StaticLogतो आपका कोड ऐसा दिखेगा :

Log.info("This is an info message")
Log.debug("This is a debug message")
Log.warn("This is a warning message","WithACustomTag")
Log.error("This is an error message with an additional Exception for output", "AndACustomTag", exception )

Log.logLevel = LogLevel.WARN
Log.info("This message will not be shown")\

यदि आप लॉगिंग विधि के लिए आउटपुट स्वरूप को परिभाषित करना चाहते हैं तो दूसरा समाधान बेहतर हो सकता है:

Log.newFormat {
    line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
}

या फ़िल्टर का उपयोग करें, उदाहरण के लिए:

Log.filterTag = "filterTag"
Log.info("This log will be filtered out", "otherTag")
Log.info("This log has the right tag", "filterTag")

timberkt

यदि आप पहले से ही जेक व्हार्टन के Timberलॉगिंग लाइब्रेरी चेक का उपयोग कर चुके हैं timberkt

यह लाइब्रेरी टिम्बर पर एपीआई के साथ बनाता है जो कोटलिन से उपयोग करना आसान है। स्वरूपण मापदंडों का उपयोग करने के बजाय, आप एक लंबो पास करते हैं जो केवल मूल्यांकन किया जाता है यदि संदेश लॉग होता है।

कोड उदाहरण:

// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())

// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }

चेक भी करें : कोटलिन और एंड्रॉइड में लॉगिंग: एककोलॉगर बनाम कोटलिन-लॉगिंग

आशा है कि यह मदद करेगा


4

क्या आपके लिए यह काम पसंद आएगा?

class LoggerDelegate {

    private var logger: Logger? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
        if (logger == null) logger = Logger.getLogger(thisRef!!.javaClass.name)
        return logger!!
    }

}

fun logger() = LoggerDelegate()

class Foo { // (by the way, everything in Kotlin is public by default)
    companion object { val logger by logger() }
}

1
इस उत्तर को और अधिक स्पष्टीकरण की आवश्यकता है, यदि पूछने वाला व्यक्ति साथी वस्तुओं को नहीं समझता है, तो उन्होंने शायद प्रतिनिधियों को नहीं दिया है, और इसलिए यह नहीं जान पाएंगे कि यह क्या कर रहा है। प्लस में इस मॉडल का उपयोग करके कोड में बहुत कम बचत है। और मुझे संदेह है कि साथी ऑब्जेक्ट में कैशिंग वास्तव में एंड्रॉइड जैसे छोटे सीपीयू के साथ एक प्रतिबंधित प्रणाली के अलावा एक प्रदर्शन लाभ है।
जैसन मिनार्ड

1
उपरोक्त यह कोड जो दिखा रहा है वह एक वर्ग का निर्माण है जो एक प्रतिनिधि के रूप में कार्य करता है (देखें kotlinlang.org/docs/reference/delegated-properties.html ) जो प्रथम श्रेणी है LoggerDelegate और फिर यह एक शीर्ष स्तर का निर्माण कर रहा है जो इसे बना रहा है प्रतिनिधि का उदाहरण बनाना आसान है (बहुत आसान नहीं है, लेकिन थोड़ा सा)। और उस फ़ंक्शन को बदल दिया जाना चाहिए inline। फिर यह जब भी कोई वांछित हो लकड़हारा प्रदान करने के लिए प्रतिनिधि का उपयोग करता है। लेकिन यह साथी के लिए एक प्रदान करता है Foo.Companionऔर वर्ग के लिए Fooऐसा नहीं है जैसा कि शायद इरादा नहीं है।
जैसन मिनार्ड

@JaysonMinard मैं सहमत हूं, लेकिन मैं भविष्य के दर्शकों के लिए जवाब छोड़ दूंगा जो "त्वरित सुधार" या अपने स्वयं के प्रोजेक्ट पर इसे लागू करने के तरीके का एक उदाहरण चाहते हैं। मुझे समझ नहीं आ रहा है कि यदि लंबोदर मौजूद नहीं हैं तो logger()फ़ंक्शन क्यों होना चाहिए inline। IntelliJ का सुझाव है कि इस मामले में अशुद्धि
Jire

1
मैंने आपके उत्तर को अपने में शामिल कर लिया, और कस्टम डेलीगेट वर्ग को हटाकर इसे सरल कर दिया और Lazyइसके बजाय एक आवरण का उपयोग किया । एक चाल के साथ यह जानने के लिए कि यह किस वर्ग के भीतर है।
जैसन मिनार्ड

1

मैंने इस संबंध में कोई मुहावरा नहीं सुना है। बेहतर सरल है, इसलिए मैं एक शीर्ष-स्तरीय संपत्ति का उपयोग करूंगा

val logger = Logger.getLogger("package_name")

यह अभ्यास पायथन में अच्छी तरह से कार्य करता है, और जैसा कि कोटलिन और पायथन अलग-अलग दिखाई दे सकते हैं, मेरा मानना ​​है कि वे "आत्मा" (मुहावरों की बात) में काफी समान हैं।


शीर्ष-स्तर को पैकेज-स्तर के रूप में भी जाना जाता है।
Caelum

एक शीर्ष स्तर चर "वैश्विक चर का उपयोग करें" कहने जैसा है और मुझे लगता है कि यह केवल तभी लागू होगा जब आपके पास अन्य शीर्ष स्तर के कार्य होंगे जो लकड़हारे का उपयोग करने के लिए आवश्यक थे। उस बिंदु पर, किसी लकड़हारे को किसी भी उपयोगिता फ़ंक्शन में पारित करना बेहतर हो सकता है जो लॉग करना चाहता है।
जैसन मिनार्ड

1
@JaysonMinard मुझे लगता है कि एक पैरामीटर के रूप में लकड़हारा गुजरना एक विरोधी पैटर्न होगा, क्योंकि आपके लॉगिंग को कभी भी आपके एपीआई, बाहरी या आंतरिक को प्रभावित नहीं करना चाहिए
वोडन 12

ठीक है, फिर मेरी बात पर, क्लास लेवल लॉगिंग के लिए क्लास में लकड़हारे को रखा, न कि टॉप लेवल फंक्शन को।
जैसन मिनार्ड

1
@voddan कम से कम एक पूर्ण उदाहरण प्रदान करते हैं कि आप किस प्रकार का लकड़हारा पैदा कर रहे हैं। val log = what?!? ... नाम से एक लकड़हारा बना? इस तथ्य को नजरअंदाज करते हुए कि वह एक विशिष्ट वर्ग के लिए एक लकड़हारा बनाना चाहता था, इस सवाल को नजरअंदाज करते हुएLoggerFactory.getLogger(Foo.class);
जैसन माइनर

1

इसके बजाय कक्षा में एक विस्तार समारोह के बारे में क्या? इस तरह से आप के साथ अंत:

public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)

class SomeClass {
    val LOG = SomeClass::class.logger()
}

नोट - मैंने इसका बिल्कुल भी परीक्षण नहीं किया है, इसलिए यह काफी सही नहीं हो सकता है।


1

सबसे पहले, आप लकड़हारा निर्माण के लिए विस्तार कार्य जोड़ सकते हैं।

inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java)
fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass)

तब आप निम्नलिखित कोड का उपयोग करके एक लकड़हारा बना पाएंगे।

private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()

दूसरा, आप एक इंटरफ़ेस परिभाषित कर सकते हैं जो एक लकड़हारा और उसके मिश्रण का कार्यान्वयन प्रदान करता है।

interface LoggerAware {
  val logger: Logger
}

class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware {
  override val logger: Logger = LoggerFactory.getLogger(containerClass)
}

inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java)

इस इंटरफ़ेस का उपयोग निम्न तरीके से किया जा सकता है।

class SomeClass : LoggerAware by loggerAware<SomeClass>() {
  // Now you can use a logger here.
}


1

यहां पहले से ही कई शानदार जवाब हैं, लेकिन उन सभी में एक वर्ग के लिए लकड़हारा जोड़ने की चिंता है, लेकिन आप ऐसा कैसे करेंगे जो शीर्ष स्तर के कार्यों में लॉगिंग करेंगे?

यह दृष्टिकोण दोनों वर्गों, साथी वस्तुओं और शीर्ष स्तर के कार्यों में अच्छी तरह से काम करने के लिए सामान्य और सरल है:

package nieldw.test

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.junit.jupiter.api.Test

fun logger(lambda: () -> Unit): Lazy<Logger> = lazy { LogManager.getLogger(getClassName(lambda.javaClass)) }
private fun <T : Any> getClassName(clazz: Class<T>): String = clazz.name.replace(Regex("""\$.*$"""), "")

val topLog by logger { }

class TopLevelLoggingTest {
    val classLog by logger { }

    @Test
    fun `What is the javaClass?`() {
        topLog.info("THIS IS IT")
        classLog.info("THIS IS IT")
    }
}

0

यही कारण है कि सामान्य रूप से: स्थैतिक सामान को बदलने के लिए साथी वस्तुएं हैं।


एक साथी ऑब्जेक्ट एक स्थिर नहीं है, यह एक सिंगलटन है जो सदस्यों को पकड़ सकता है जो JvmStaticएनोटेशन का उपयोग करने पर स्थिर हो सकते हैं । और भविष्य में एक से अधिक अनुमत हो सकते हैं। अधिक जानकारी या नमूने के बिना यह उत्तर बहुत उपयोगी नहीं है।
जैसन मिनार्ड

मैंने यह नहीं कहा कि यह एक स्थैतिक था। मैंने कहा कि यह स्टैटिक्स की जगह के लिए है। और एक से अधिक की अनुमति क्यों होगी? इसका कोई मतलब नहीं है। अंत में, मैं जल्दी में था, और मुझे लगा कि सही दिशा में इशारा करना काफी मददगार होगा।
याकूब ज़िम्मरमैन

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

0

Slf4j उदाहरण, दूसरों के लिए समान। यह भी पैकेज स्तर लकड़हारा बनाने के लिए काम करता है

/**  
  * Get logger by current class name.  
  */ 

fun getLogger(c: () -> Unit): Logger = 
        LoggerFactory.getLogger(c.javaClass.enclosingClass)

उपयोग:

val logger = getLogger { }

0
fun <R : Any> R.logger(): Lazy<Logger> = lazy { 
    LoggerFactory.getLogger((if (javaClass.kotlin.isCompanion) javaClass.enclosingClass else javaClass).name) 
}

class Foo {
    val logger by logger()
}

class Foo {
    companion object {
        val logger by logger()
    }
}

0

यह अभी भी WIP (लगभग समाप्त हो चुका है) इसलिए मैं इसे साझा करना चाहूंगा: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

इस पुस्तकालय का मुख्य लक्ष्य एक परियोजना के दौरान एक निश्चित लॉग शैली को लागू करना है। यह कोटलिन कोड उत्पन्न करके मैं इस प्रश्न में वर्णित कुछ मुद्दों को दूर करने का प्रयास कर रहा हूं। मूल प्रश्न के संबंध में जो मैं आमतौर पर करता हूं वह है:

private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {

}

0

आप बस उपयोगिताओं की अपनी "लाइब्रेरी" का निर्माण कर सकते हैं। इस कार्य के लिए आपको एक बड़े पुस्तकालय की आवश्यकता नहीं है जो आपकी परियोजना को भारी और जटिल बना देगा।

उदाहरण के लिए, आप किसी भी वर्ग की संपत्ति का नाम, प्रकार और मूल्य प्राप्त करने के लिए कोटलिन प्रतिबिंब का उपयोग कर सकते हैं।

सबसे पहले, सुनिश्चित करें कि आप अपने build.gradle में मेटा-निर्भरता बसे हैं:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

बाद में, आप इस कोड को अपनी परियोजना में कॉपी और पेस्ट कर सकते हैं:

import kotlin.reflect.full.declaredMemberProperties

class LogUtil {
    companion object {
        /**
         * Receives an [instance] of a class.
         * @return the name and value of any member property.
         */
        fun classToString(instance: Any): String {
            val sb = StringBuilder()

            val clazz = instance.javaClass.kotlin
            clazz.declaredMemberProperties.forEach {
                sb.append("${it.name}: (${it.returnType}) ${it.get(instance)}, ")
            }

            return marshalObj(sb)
        }

        private fun marshalObj(sb: StringBuilder): String {
            sb.insert(0, "{ ")
            sb.setLength(sb.length - 2)
            sb.append(" }")

            return sb.toString()
        }
    }
}

उपयोग का उदाहरण:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.