วิธีการเข้าสู่ระบบที่เป็นเอกลักษณ์ใน Kotlin


164

Kotlin ไม่มีความคิดแบบเดียวกับที่ใช้ในจาวา ใน Java วิธีการบันทึกที่ยอมรับโดยทั่วไปคือ:

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

คำถามคือวิธีการแสดงการบันทึกใน Kotlin คืออะไร?


1
การไม่โพสต์สิ่งนี้เป็นคำตอบทำให้อยู่ไกลจากวิธีของ Java แต่ฉันได้พิจารณาการเขียนฟังก์ชันส่วนขยายบน Any สำหรับการบันทึก คุณต้องแคช Loggers แน่นอน แต่ฉันคิดว่านี่จะเป็นวิธีที่ดีในการทำเช่นนั้น
mhlz

1
@mhlz ฟังก์ชันส่วนขยายนั้นจะไม่ได้รับการแก้ไขแบบคงที่หรือไม่ ในขณะที่มันจะไม่ถูกนำไปใช้กับวัตถุทั้งหมดเฉพาะกับประเภทAny(ดังนั้นต้องใช้นักแสดง)
Jire

1
@mhlz ฟังก์ชั่นส่วนขยายไม่สมเหตุสมผลเพราะมันไม่มีสถานะที่จะเก็บ logger ไว้ อาจเป็นส่วนขยายเพื่อส่งคืนตัวบันทึก แต่ทำไมต้องมีในคลาสที่รู้จักทุกตัวในระบบ การใส่ส่วนขยายใด ๆ มีแนวโน้มที่จะกลายเป็นเสียงเลอะเทอะใน IDE ในภายหลัง @Jire ส่วนขยายจะใช้กับลูกหลานของทุกคนจะยังคงกลับมาที่ถูกต้องthis.javaClassสำหรับแต่ละคน แต่ฉันไม่แนะนำให้ใช้เป็นวิธีแก้ปัญหา
Jayson Minard

คำตอบ:


251

ในรหัส Kotlin ที่เป็นผู้ใหญ่ส่วนใหญ่คุณจะพบหนึ่งในรูปแบบเหล่านี้ด้านล่าง วิธีการใช้Property Delegatesใช้ประโยชน์จากพลังของ Kotlin ในการสร้างรหัสที่เล็กที่สุด

หมายเหตุ: รหัสที่นี่มีไว้สำหรับjava.util.Loggingแต่ทฤษฎีเดียวกันนี้ใช้กับไลบรารีการบันทึกใด ๆ

คงเหมือน (ปกติเทียบเท่ากับรหัส Java ของคุณในคำถาม)

หากคุณไม่สามารถไว้วางใจในประสิทธิภาพของการค้นหาแฮชนั้นในระบบการบันทึกคุณสามารถรับพฤติกรรมที่คล้ายกับโค้ด Java ของคุณโดยใช้ออบเจกต์ร่วมที่สามารถเก็บอินสแตนซ์และรู้สึกว่าเป็นแบบคงที่สำหรับคุณ

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

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

สร้างผลลัพธ์:

26 ธันวาคม 2558 11:28:32 น. org.stackoverflow.kotlin.test.MyClassfoo INFO: สวัสดีจาก MyClass

เพิ่มเติมเกี่ยวกับสหายวัตถุที่นี่: Companion วัตถุ ... นอกจากนี้ยังทราบว่าในตัวอย่างข้างต้นMyClass::class.javaได้รับตัวอย่างของชนิดClass<MyClass>สำหรับตัดไม้ในขณะที่จะได้รับตัวอย่างของประเภทthis.javaClassClass<MyClass.Companion>

ตามอินสแตนซ์ของคลาส (ทั่วไป)

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

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

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

สร้างผลลัพธ์:

26 ธันวาคม 2558 11:28:44 น. org.stackoverflow.kotlin.test.MyClass foo INFO: สวัสดีจาก MyClass

คุณสามารถทดสอบประสิทธิภาพได้ทั้งแบบต่ออินสแตนซ์และแบบต่อคลาสและดูว่ามีความแตกต่างที่เหมือนจริงหรือไม่สำหรับแอพส่วนใหญ่

ตัวแทนอสังหาริมทรัพย์ (ธรรมดาและหรูหราที่สุด)

อีกวิธีหนึ่งซึ่งแนะนำโดย @Jire ในคำตอบอื่นคือการสร้างตัวแทนผู้ให้บริการซึ่งคุณสามารถใช้เพื่อทำตรรกะอย่างสม่ำเสมอในคลาสอื่น ๆ ที่คุณต้องการ มีวิธีที่ง่ายกว่าในการทำเช่นนี้เนื่องจาก Kotlin 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"

รหัสนี้ยังตรวจสอบให้แน่ใจว่าถ้าคุณใช้ในวัตถุ Companion ว่าชื่อคนตัดไม้จะเป็นเช่นเดียวกับถ้าคุณใช้มันในชั้นเรียนของตัวเอง ตอนนี้คุณสามารถ:

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()คลาสทั้งสองนี้จะเป็น:

26 ธันวาคม 2558 11:30:55 น. org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something

26 ธันวาคม 2558 11:30:55 น. org.stackoverflow.kotlin.test.SomethingElse foo INFO: สวัสดีจาก SomethingElse

ฟังก์ชั่นการขยาย (เรื่องแปลกในกรณีนี้เพราะ "มลภาวะ" ของเนมสเปซใด ๆ )

Kotlin มีเทคนิคซ่อนเร้นบางอย่างที่ช่วยให้คุณทำรหัสนี้ให้เล็กลงได้ คุณสามารถสร้างฟังก์ชั่นเสริมในชั้นเรียนและทำให้พวกเขามีฟังก์ชั่นเพิ่มเติม หนึ่งข้อเสนอแนะในความคิดเห็นข้างต้นคือการขยายAnyด้วยฟังก์ชั่นคนตัดไม้ สิ่งนี้สามารถสร้างเสียงรบกวนเมื่อมีคนใช้รหัสเสร็จใน IDE ของพวกเขาในชั้นเรียนใด ๆ แต่มีข้อดีที่เป็นความลับในการขยาย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")
    }
}

การผลิตการส่งออก:

26 ธันวาคม 2558 11:29:12 น. org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: สวัสดีจาก SomethingDifferent

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()
}

การผลิตการส่งออก:

26 ธันวาคม 2558 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: สวัสดีจาก MarkedClass

หากคุณต้องการบังคับให้สร้างเขตข้อมูลที่เหมือนกันเพื่อเก็บ logger ไว้ในขณะที่ใช้อินเทอร์เฟซนี้คุณอาจต้องการให้ implementer มีฟิลด์เช่น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!
    }
}

การรวมเข้าด้วยกัน (ไลบรารีผู้ช่วยขนาดเล็ก)

นี่คือห้องสมุดผู้ช่วยเล็ก ๆ ที่จะทำให้ตัวเลือกใด ๆ ข้างต้นใช้งานง่าย เป็นเรื่องปกติใน Kotlin ที่จะขยาย API เพื่อให้ตรงกับความต้องการของคุณ ทั้งในส่วนขยายหรือฟังก์ชั่นระดับบนสุด นี่คือส่วนประสมเพื่อให้คุณมีตัวเลือกสำหรับวิธีการสร้างตัวบันทึกและตัวอย่างแสดงรูปแบบทั้งหมด:

// 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 ตัวของตัวบันทึกที่สร้างในตัวอย่างนี้จะสร้างชื่อตัวบันทึกและการส่งออกเดียวกัน

26 ธันวาคม 2558 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo ข้อมูล: สวัสดีจาก MixedBagOfTricks

หมายเหตุ:unwrapCompanionClass()วิธีการเพื่อให้แน่ใจว่าเราไม่ได้สร้างคนตัดไม้ตั้งชื่อตามวัตถุสหาย แต่ระดับปิดล้อม นี่เป็นวิธีที่แนะนำในปัจจุบันเพื่อค้นหาคลาสที่มีวัตถุที่แสดงร่วม การตัดทอน " $ Companion " จากชื่อที่ใช้removeSuffix()ไม่ทำงานเนื่องจากออบเจ็กต์ที่แสดงร่วมสามารถให้ชื่อแบบกำหนดเองได้


กรอบการฉีดพึ่งพาบางคนใช้ผู้ได้รับมอบหมายอย่างที่คุณเห็นในคำตอบอื่นที่นี่ ดูเหมือนว่า `val log: Logger โดย injectLogger ()` และอนุญาตให้ระบบการบันทึกถูกฉีดและไม่รู้จักรหัสที่ใช้ (กรอบการฉีดของฉันแสดงสิ่งนี้อยู่ที่github.com/kohesive/injekt )
Jayson Minard

10
ขอบคุณสำหรับคำตอบมากมาย ข้อมูลมาก โดยเฉพาะอย่างยิ่งผมชอบ ผู้ได้รับมอบหมายอสังหาริมทรัพย์ (ทั่วไป, สง่างามมากที่สุด)การดำเนินงาน
mchlstckl

6
ฉันคิดว่ามีการเปลี่ยนแปลงใน kotlin syntax และการแกะควรจะเป็นofClass.enclosingClass.kotlin.objectInstance?.javaClassแทนofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
อาไม่เป็นไรตามที่ระบุไว้ที่นี่kotlinlang.org/docs/reference/reflection.html jar แบบสะท้อนแสงถูกส่งแยกต่างหากจาก stdlib สำหรับ gradle เราต้องการสิ่งนี้:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
รหัสสำหรับการสร้าง 'ตัวแทนที่ได้รับมอบหมาย' และ 'ฟังก์ชั่นส่วนขยาย' ดูเหมือนจะเป็นรหัสเดียวกันยกเว้นประเภทที่ส่งคืน ตัวอย่างโค้ดสำหรับ Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) ปรากฏขึ้นเพื่อสร้างฟังก์ชั่นส่วนขยายซึ่ง"".logger()ขณะนี้เป็นเรื่องนี้เป็นสิ่งที่ควรทำเช่นนี้หรือไม่?
Mike Rylander

32

ลองดูที่ไลบรารีการบันทึก kotlin
อนุญาตการบันทึกเช่นนี้:

private val logger = KotlinLogging.logger {}

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

หรือเช่นนั้น:

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

ฉันยังเขียนโพสต์บล็อกเปรียบเทียบกับAnkoLogger: การเข้าสู่ระบบใน Kotlin & Android: AnkoLogger เทียบกับการบันทึก kotlin

คำเตือน: ฉันเป็นผู้ดูแลห้องสมุดนั้น

แก้ไข: kotlin-logging ตอนนี้มีการสนับสนุนหลายแพลตฟอร์ม: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


ฉันขอแนะนำให้คุณแก้ไขคำตอบของคุณเพื่อแสดงผลลัพธ์ของการlogger.info()โทรเช่นเดียวกับ Jayson ในคำตอบที่เขายอมรับ
Paulo Merson

7

เป็นตัวอย่างที่ดีของการใช้งานการบันทึกฉันต้องการพูดถึงAnkoซึ่งใช้อินเทอร์เฟซพิเศษAnkoLoggerซึ่งคลาสที่ต้องมีการบันทึกควรใช้ ภายในอินเตอร์เฟสมีรหัสที่สร้างแท็กการบันทึกสำหรับชั้นเรียน การบันทึกจะทำผ่านฟังก์ชั่นส่วนขยายซึ่งสามารถเรียกใช้ในการใช้งาน interace โดยไม่มีส่วนนำหน้าหรือแม้แต่การสร้างอินสแตนซ์ของตัวบันทึก

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


รหัสด้านล่างนั้นเป็นพื้นAnkoLoggerง่ายและเขียนใหม่สำหรับการใช้งานที่ไม่เชื่อเรื่องพระเจ้า

ก่อนอื่นมีอินเทอร์เฟซที่ทำงานเหมือนอินเทอร์เฟซตัวทำเครื่องหมาย:

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()
            })

สิ่งเหล่านี้ถูกกำหนดครั้งเดียวสำหรับทั้งการบันทึกข้อความและการบันทึก a Throwableเช่นกันสิ่งนี้ทำด้วยthrowableพารามิเตอร์ทางเลือก

ฟังก์ชั่นที่ถูกส่งผ่านhandlerและthrowableHandlerอาจแตกต่างกันสำหรับวิธีการบันทึกที่แตกต่างกันตัวอย่างเช่นพวกเขาสามารถเขียนบันทึกไปยังไฟล์หรืออัปโหลดที่อื่น isLoggingEnabledและLoggingLevelsถูกละไว้เพื่อความกะทัดรัด แต่การใช้มันให้ความยืดหยุ่นมากกว่า


จะช่วยให้การใช้งานดังต่อไปนี้:

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

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

private object MyPackageLog : MyLogger

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

คำตอบนี้เป็นเฉพาะสำหรับ Android และคำถามไม่ได้กล่าวถึงหรือไม่มีแท็ก Android
Jayson Minard

@JaysonMinard ทำไมมันคืออะไร? วิธีการนี้มีวัตถุประสงค์ทั่วไปเช่นการมีแท็กการบันทึกที่ไม่ซ้ำกันสำหรับทุกคลาสนั้นมีประโยชน์ในโครงการที่ไม่ใช่ Android เช่นกัน
ปุ่มลัด

1
ไม่ชัดเจนว่าคุณกำลังพูดว่า "ใช้สิ่งที่คล้ายกับสิ่งที่ Anko ทำ" และดูเหมือนว่า "ใช้ Anko" แทน ... ซึ่งต้องใช้ไลบรารี Android ที่เรียกว่า Anko ซึ่งมีอินเทอร์เฟซที่มีฟังก์ชันส่วนขยายที่เรียกandroid.util.Logให้ทำการบันทึก ความตั้งใจของคุณคืออะไร? ใช้ Anko? ในการสร้างสิ่งที่คล้ายกันในขณะที่ใช้ Anko เป็นตัวอย่าง (จะดีกว่าถ้าคุณใส่โค้ดที่แนะนำแบบอินไลน์และแก้ไขมันสำหรับผู้ที่ไม่ใช่ Android แทนที่จะพูดว่า "port this to non-Android นี่คือลิงค์" แทนการเพิ่มโค้ดตัวอย่าง โทรหา Anko)
Jayson Minard

1
@JaysonMinard ขอบคุณสำหรับความคิดเห็นของคุณฉันได้เขียนโพสต์ใหม่เพื่อให้อธิบายถึงวิธีการได้มากกว่าการอ้างอิง Anko
ปุ่มลัด

6

KISS: สำหรับการย้ายทีม Java ไปยัง Kotlin

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

import org.slf4j.LoggerFactory

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

นี้ใช้ Kotlin reified พารามิเตอร์ชนิด

ตอนนี้คุณสามารถใช้สิ่งนี้ได้ดังนี้:

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

วิธีนี้ง่ายสุด ๆ และใกล้เคียงกับค่าเทียบเท่าของจาวา แต่เพิ่งเติมน้ำตาลประโยค

ขั้นตอนถัดไป: ส่วนขยายหรือผู้รับมอบสิทธิ์

โดยส่วนตัวแล้วฉันชอบที่จะก้าวไปอีกขั้นหนึ่งและใช้ส่วนขยายหรือผู้ได้รับมอบหมาย สรุปได้อย่างนี้ในคำตอบของ @ JaysonMinard แต่นี่คือ TL; DR สำหรับแนวทาง "Delegate" ด้วย log4j2 API ( UPDATE : ไม่จำเป็นต้องเขียนโค้ดนี้ด้วยตนเองอีกต่อไปเนื่องจากมันได้เปิดตัวเป็นโมดูลอย่างเป็นทางการของ โครงการ log4j2 ดูด้านล่าง) ตั้งแต่ log4j2 ซึ่งแตกต่างจาก slf4j รองรับการบันทึกด้วยSupplier's ฉันจึงได้เพิ่มผู้ร่วมประชุมเพื่อทำให้การใช้วิธีการเหล่านี้ง่ายขึ้น

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 Kotlin Logging API

ส่วนก่อนหน้านี้ส่วนใหญ่ได้รับการดัดแปลงโดยตรงเพื่อผลิตโมดูลKotlin Logging APIซึ่งตอนนี้เป็นส่วนหนึ่งอย่างเป็นทางการของ Log4j2 (ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียนหลัก) คุณสามารถดาวน์โหลดนี้โดยตรงจาก Apacheหรือผ่านทางMaven กลาง

การใช้งานเป็นไปตามที่อธิบายไว้ข้างต้น แต่โมดูลรองรับทั้งการเข้าถึงตัวบันทึกตามอินเตอร์เฟสloggerฟังก์ชันขยายAnyสำหรับการใช้งานเมื่อthisมีการกำหนดและฟังก์ชั่นบันทึกชื่อที่ใช้สำหรับการใช้งานที่ไม่มีการthisกำหนด


1
ถ้าฉันถูกต้องคุณสามารถหลีกเลี่ยงการพิมพ์ชื่อคลาสในโซลูชันแรกที่คุณให้ไว้โดยเปลี่ยนวิธีการลงชื่อเป็น T.logger ()
IPat

1
@IPat yup โซลูชันแรกโดยเจตนาไม่ได้ทำเพื่อให้อยู่ใกล้กับ "วิธี java" ส่วนที่สองของคำตอบครอบคลุมกรณีส่วนขยายT.logger()- ดูที่ด้านล่างของตัวอย่างรหัส
Raman

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 การเข้าสู่ระบบ

การบันทึก kotlin ( โครงการ Github - การบันทึก kotlin ) ช่วยให้คุณสามารถเขียนรหัสการบันทึกดังนี้:

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

StaticLog

หรือคุณสามารถใช้ขนาดเล็กที่เขียนในไลบรารี Kotlin ที่เรียกว่า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การตรวจสอบไลบรารีการบันทึกของ Jake Wharton timberktแล้ว

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

ตัวอย่างรหัส:

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

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

ตรวจสอบด้วย: การเข้าสู่ระบบ Kotlin & Android: AnkoLogger เทียบกับการบันทึก kotlin

หวังว่ามันจะช่วย


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
คำตอบนี้ต้องการคำอธิบายเพิ่มเติมหากผู้ขอไม่เข้าใจวัตถุที่เป็นเพื่อนพวกเขาอาจไม่ได้รับมอบหมายและดังนั้นจึงไม่รู้ว่าสิ่งนี้กำลังทำอะไรอยู่ นอกจากนี้ยังมีรหัสประหยัดน้อยมากเมื่อใช้รุ่นนี้ และฉันสงสัยว่าการแคชในวัตถุที่เป็นคู่หูนั้นจริงๆแล้วประสิทธิภาพเพิ่มขึ้นนอกเหนือจากในระบบที่ถูก จำกัด ด้วย CPU ขนาดเล็กเช่น Android
Jayson Minard

1
สิ่งที่รหัสด้านบนนี้แสดงคือการสร้างคลาสที่ทำหน้าที่เป็นตัวแทน (ดูkotlinlang.org/docs/reference/delegated-properties.html ) ซึ่งเป็นคลาสแรกLoggerDelegate จากนั้นจะสร้างฟังก์ชันระดับบนสุดที่สร้างขึ้น มันง่ายกว่าที่จะสร้างตัวอย่างของผู้รับมอบสิทธิ์ (ไม่มากง่ายกว่า แต่มีเพียงเล็กน้อย) inlineและฟังก์ชั่นที่ควรจะเปลี่ยนไปเป็น จากนั้นจะใช้ผู้รับมอบสิทธิ์เพื่อจัดทำบันทึกเมื่อใดก็ตามที่ต้องการ แต่ให้หนึ่งสำหรับเพื่อนFoo.Companionและไม่ใช่สำหรับชั้นเรียนFooดังนั้นอาจไม่เป็นไปตามที่ตั้งใจไว้
Jayson Minard

@JaysonMinard ฉันเห็นด้วย แต่ฉันจะทิ้งคำตอบสำหรับผู้ชมในอนาคตที่ต้องการ "แก้ไขด่วน" หรือตัวอย่างของวิธีการใช้สิ่งนี้กับโครงการของตัวเอง ฉันไม่เข้าใจว่าทำไมlogger()ฟังก์ชั่นควรจะเป็นinlineถ้าไม่มีลูกแกะอยู่ IntelliJ แนะนำให้ทำการฝังไว้ในกรณีนี้ไม่จำเป็น: i.imgur.com/YQH3NB1.png
Jire

1
เราได้นำคำตอบของคุณเข้าไปในเหมืองและง่ายมันโดยการลบระดับผู้ร่วมประชุมที่กำหนดเองและใช้เสื้อคลุมรอบLazyแทน ด้วยเคล็ดลับที่จะทำให้มันรู้ว่ามันอยู่ในระดับใด
Jayson Minard

1

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

val logger = Logger.getLogger("package_name")

การปฏิบัตินี้ทำหน้าที่ได้ดีใน Python และอาจแตกต่างไปจาก Kotlin และ Python ฉันเชื่อว่าพวกมันมีลักษณะคล้ายกันใน "วิญญาณ" (พูดถึงสำนวน)


ระดับบนสุดเรียกอีกอย่างหนึ่งว่าระดับแพ็คเกจ
Caelum

ตัวแปรระดับบนสุดก็เหมือนกับการพูดว่า "ใช้ตัวแปรทั่วโลก" และฉันคิดว่าจะใช้ได้เฉพาะเมื่อคุณมีฟังก์ชั่นระดับสูงสุดอื่น ๆ ที่จำเป็นต้องใช้ตัวบันทึก ณ จุดนั้นแม้ว่ามันจะดีกว่าที่จะผ่านในคนตัดไม้เพื่อฟังก์ชั่นยูทิลิตี้ใด ๆ ที่ต้องการเข้าสู่ระบบ
Jayson Minard

1
@JaysonMinard ฉันคิดว่าการส่งผ่านตัวบันทึกเป็นพารามิเตอร์จะเป็นรูปแบบการต่อต้านเพราะการบันทึกของคุณไม่ควรส่งผลกระทบต่อ API ของคุณภายนอกหรือภายใน
voddan

ตกลงจากนั้นกลับไปที่จุดของฉันสำหรับการบันทึกระดับชั้นให้ใส่ตัวบันทึกในชั้นเรียนไม่ใช่ฟังก์ชันระดับบนสุด
Jayson Minard

1
@voddan อย่างน้อยให้ตัวอย่างที่สมบูรณ์ของประเภทของคนตัดไม้ที่คุณกำลังสร้าง val log = what?!? ... สร้างชื่อคนตัดไม้? ไม่สนใจข้อเท็จจริงที่ว่าคำถามแสดงให้เห็นว่าเขาต้องการสร้างคนตัดไม้สำหรับชั้นเรียนที่เฉพาะเจาะจงLoggerFactory.getLogger(Foo.class);
Jayson Minard

1

ฟังก์ชั่นการขยายใน Class แทนอะไร วิธีที่คุณจะจบลงด้วย:

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

ประการที่สองคุณสามารถกำหนดอินเทอร์เฟซที่มีตัวบันทึกและการใช้งาน mixin

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คำอธิบายประกอบ และในอนาคตอาจมีมากกว่าหนึ่งรายการที่อนุญาต รวมทั้งคำตอบนี้ไม่มีประโยชน์หากไม่มีข้อมูลหรือตัวอย่างเพิ่มเติม
Jayson Minard

ฉันไม่ได้บอกว่ามันคงที่ ฉันบอกว่ามันเป็นการแทนที่สถิตยศาสตร์ และทำไมจะมีมากกว่าหนึ่งอนุญาต ไม่สมเหตุสมผลเลย ในที่สุดฉันก็รีบและฉันคิดว่าการชี้ไปในทิศทางที่ถูกต้องจะมีประโยชน์มากพอ
จาค็อบซิมเมอร์แมน

1
วัตถุร่วมไม่ได้มีไว้สำหรับแทนที่สถิตยศาสตร์ แต่มันก็สามารถทำให้องค์ประกอบของมันคงที่ Kotlin ให้การสนับสนุนมากกว่าเพื่อนสักครั้งและช่วยให้พวกเขามีชื่ออื่น เมื่อคุณเริ่มตั้งชื่อพวกเขาพวกเขาทำตัวเหมือนสถิตยศาสตร์ และมันจะเปิดทิ้งไว้ในอนาคตเพื่อมีเพื่อนที่มีชื่อมากกว่าหนึ่งคน ตัวอย่างเช่นหนึ่งอาจเป็นFactoryและอื่นHelpers
Jayson Minard

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-soon-to-come-in-version-14

เป้าหมายหลักของไลบรารีนี้คือการบังคับใช้ลักษณะการบันทึกบางอย่างในโครงการ เมื่อให้มันสร้างโค้ด Kotlin ฉันพยายามแก้ไขปัญหาที่กล่าวถึงในคำถามนี้ เกี่ยวกับคำถามต้นฉบับสิ่งที่ฉันมักจะทำคือเพียง:

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

}

0

คุณสามารถสร้าง "ห้องสมุด" ของสาธารณูปโภคเองได้ คุณไม่จำเป็นต้องมีห้องสมุดขนาดใหญ่สำหรับงานนี้ซึ่งจะทำให้โครงการของคุณหนักและซับซ้อน

ตัวอย่างเช่นคุณสามารถใช้ Kotlin Reflection เพื่อรับชื่อชนิดและค่าของคุณสมบัติคลาสใด ๆ

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