Cách đăng nhập thành ngữ trong Kotlin


164

Kotlin không có cùng khái niệm về các trường tĩnh như được sử dụng trong Java. Trong Java, cách ghi nhật ký được chấp nhận chung là:

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

Câu hỏi là cách thành ngữ để thực hiện đăng nhập trong Kotlin là gì?


1
Không đăng bài này dưới dạng câu trả lời vì nó khác xa với cách Java, nhưng tôi đã xem xét việc viết một hàm mở rộng trên Any để ghi nhật ký. Tất nhiên bạn cần phải lưu trữ Loggers, nhưng tôi nghĩ đây sẽ là một cách hay để làm điều đó.
mhlz

1
@mhlz Chức năng mở rộng có được giải quyết tĩnh không? Như trong, nó sẽ không được áp dụng cho tất cả các đối tượng, chỉ cho những đối tượng thuộc loại Any(do đó cần một diễn viên)?
Jire

1
@mhlz một chức năng mở rộng không có ý nghĩa gì vì nó sẽ không có trạng thái để ghi nhật ký. Nó có thể là một phần mở rộng để trả về một logger, nhưng tại sao lại có trên mỗi lớp đã biết trong hệ thống? Đưa các phần mở rộng vào Bất kỳ xu hướng nào trở thành tiếng ồn cẩu thả trong IDE sau này. @Jire tiện ích mở rộng sẽ áp dụng cho tất cả các hậu duệ của Any, vẫn sẽ trả lại chính xác this.javaClasscho từng phần. Nhưng tôi không đề xuất nó như một giải pháp.
Jayson Minard

Câu trả lời:


250

Trong phần lớn mã Kotlin trưởng thành, bạn sẽ tìm thấy một trong những mẫu dưới đây. Cách tiếp cận sử dụng Thuộc tính sở hữu tận dụng sức mạnh của Kotlin để tạo ra mã nhỏ nhất.

Lưu ý: mã ở đây là dành cho java.util.Loggingnhưng lý thuyết tương tự áp dụng cho bất kỳ thư viện ghi nhật ký nào

Giống như tĩnh (phổ biến, tương đương với mã Java của bạn trong câu hỏi)

Nếu bạn không thể tin tưởng vào hiệu suất của tra cứu băm đó bên trong hệ thống ghi nhật ký, bạn có thể có hành vi tương tự với mã Java của mình bằng cách sử dụng một đối tượng đồng hành có thể giữ một cá thể và cảm thấy như một động tĩnh đối với bạn.

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

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

tạo đầu ra:

Ngày 26 tháng 12 năm 2015 11:28:32 AM org.stackoverflow.kotlin.test.MyClassfoo THÔNG TIN: Xin chào từ MyClass

Thông tin thêm về các đối tượng đồng hành ở đây: Đối tượng đồng hành ... Cũng lưu ý rằng trong mẫu ở trên MyClass::class.javacó thể hiện loại Class<MyClass>cho trình ghi, trong khi đó this.javaClasssẽ có thể hiện của loại Class<MyClass.Companion>.

Mỗi trường hợp của một lớp (phổ biến)

Nhưng, thực sự không có lý do gì để tránh gọi và nhận logger ở cấp độ cá thể. Cách thức thành ngữ Java mà bạn đề cập đã lỗi thời và dựa trên nỗi sợ hiệu năng, trong khi đó trình ghi nhật ký cho mỗi lớp đã được lưu trữ bởi hầu hết mọi hệ thống ghi nhật ký hợp lý trên hành tinh. Chỉ cần tạo một thành viên để giữ đối tượng logger.

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

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

tạo đầu ra:

Ngày 26 tháng 12 năm 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo THÔNG TIN: Xin chào từ MyClass

Bạn có thể kiểm tra hiệu suất cả hai biến thể cho mỗi phiên bản và mỗi lớp và xem liệu có sự khác biệt thực tế cho hầu hết các ứng dụng hay không.

Đại biểu tài sản (phổ biến, thanh lịch nhất)

Một cách tiếp cận khác, được đề xuất bởi @Jire trong một câu trả lời khác, là tạo một đại biểu thuộc tính, sau đó bạn có thể sử dụng để thực hiện logic thống nhất trong bất kỳ lớp nào khác mà bạn muốn. Có một cách đơn giản hơn để làm điều này vì Kotlin đã cung cấp một Lazyđại biểu rồi, chúng ta có thể chỉ cần bọc nó trong một hàm. Một mẹo ở đây là nếu chúng ta muốn biết loại lớp hiện đang sử dụng đại biểu, chúng ta biến nó thành một hàm mở rộng trên bất kỳ lớp nào:

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"

Mã này cũng đảm bảo rằng nếu bạn sử dụng nó trong Đối tượng đồng hành thì tên logger sẽ giống như khi bạn sử dụng nó trên chính lớp đó. Bây giờ bạn có thể chỉ cần:

class Something {
    val LOG by logger()

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

cho mỗi thể hiện của lớp hoặc nếu bạn muốn nó tĩnh hơn với một thể hiện trên mỗi lớp:

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

    }

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

Và đầu ra của bạn từ việc gọi foo()cả hai lớp này sẽ là:

Ngày 26 tháng 12 năm 2015 11:30:55 AM org.stackoverflow.kotlin.test.S Something foo INFO: Xin chào từ Something

Ngày 26 tháng 12 năm 2015 11:30:55 AM org.stackoverflow.kotlin.test.S SomethingElse foo THÔNG TIN: Xin chào từ SomethingElse

Hàm mở rộng (không phổ biến trong trường hợp này vì "ô nhiễm" của bất kỳ không gian tên nào)

Kotlin có một vài thủ thuật ẩn cho phép bạn tạo một số mã này thậm chí còn nhỏ hơn. Bạn có thể tạo các hàm mở rộng trên các lớp và do đó cung cấp cho chúng chức năng bổ sung. Một gợi ý trong các ý kiến ​​trên là mở rộng Anyvới chức năng logger. Điều này có thể tạo ra tiếng ồn bất cứ lúc nào ai đó sử dụng hoàn thành mã trong IDE của họ trong bất kỳ lớp nào. Nhưng có một lợi ích bí mật đối với việc mở rộng Anyhoặc một số giao diện đánh dấu khác: bạn có thể ngụ ý rằng bạn đang mở rộng lớp của riêng mình và do đó phát hiện lớp bạn đang ở trong đó. Huh? Để ít gây nhầm lẫn, đây là mã:

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

Bây giờ trong một lớp (hoặc đối tượng đồng hành), tôi chỉ có thể gọi phần mở rộng này trên lớp của riêng mình:

class SomethingDifferent {
    val LOG = logger()

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

Sản lượng sản xuất:

Ngày 26 tháng 12 năm 2015 11:29:12 AM org.stackoverflow.kotlin.test.S SomethingDifferent foo THÔNG TIN: Xin chào từ SomethingDifferent

Về cơ bản, mã được xem như một lời kêu gọi gia hạn Something.logger(). Vấn đề là những điều sau đây cũng có thể đúng là tạo ra "ô nhiễm" trên các lớp khác:

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

Các chức năng mở rộng trên Giao diện đánh dấu (không chắc mức độ phổ biến, nhưng mô hình chung cho "đặc điểm")

Để sử dụng tiện ích mở rộng sạch hơn và giảm "ô nhiễm", bạn có thể sử dụng giao diện đánh dấu để mở rộng:

interface Loggable {} 

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

Hoặc thậm chí làm cho phần phương thức của giao diện với cài đặt mặc định:

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

Và sử dụng một trong những biến thể này trong lớp của bạn:

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

Sản lượng sản xuất:

Ngày 26 tháng 12 năm 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo THÔNG TIN: Xin chào từ MarkedClass

Nếu bạn muốn buộc tạo một trường thống nhất để giữ bộ ghi, thì trong khi sử dụng giao diện này, bạn có thể dễ dàng yêu cầu người thực hiện phải có một trường như LOG:

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

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

Bây giờ người thực hiện giao diện phải trông như thế này:

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

Tất nhiên, một lớp cơ sở trừu tượng có thể làm tương tự, có tùy chọn cả giao diện và lớp trừu tượng thực hiện giao diện đó cho phép linh hoạt và đồng nhất:

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!
    }
}

Kết hợp tất cả lại với nhau (Một thư viện trợ giúp nhỏ)

Dưới đây là một thư viện trợ giúp nhỏ để làm cho bất kỳ tùy chọn nào ở trên dễ sử dụng. Điều phổ biến ở Kotlin là mở rộng API để làm cho chúng phù hợp hơn với sở thích của bạn. Hoặc trong các chức năng mở rộng hoặc cấp cao nhất. Dưới đây là một kết hợp để cung cấp cho bạn các tùy chọn về cách tạo logger và một mẫu hiển thị tất cả các biến thể:

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

Chọn bất kỳ tùy chọn nào bạn muốn giữ và đây là tất cả các tùy chọn đang sử dụng:

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

Tất cả 13 phiên bản của logger được tạo trong mẫu này sẽ tạo ra cùng một tên logger và đầu ra:

Ngày 26 tháng 12 năm 2015 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo THÔNG TIN: Xin chào từ MixedBagOfTricks

Lưu ý: Các unwrapCompanionClass()chắc chắn method mà chúng ta không tạo ra một logger đặt theo tên của đối tượng bạn đồng hành nhưng thay vì lớp kèm theo. Đây là cách được đề xuất hiện tại để tìm lớp chứa đối tượng đồng hành. Tước " $ Đồng hành " khỏi tên bằng cách sử dụng removeSuffix()không hoạt động vì các đối tượng đồng hành có thể được đặt tên tùy chỉnh.


Một số khung tiêm phụ thuộc sử dụng các đại biểu như bạn thấy trong câu trả lời khác ở đây. Chúng trông giống như `val log: Logger by chíchLogger ()` và cho phép hệ thống ghi nhật ký được tiêm và không biết mã sử dụng. (Khung tiêm của tôi cho thấy điều này là tại github.com/kohesive/injekt )
Jayson Minard

10
Cảm ơn câu trả lời sâu rộng. Rất nhiều thông tin. Tôi đặc biệt thích cách triển khai Thuộc tính (phổ biến, thanh lịch nhất) .
mchlstckl

6
Tôi nghĩ rằng có một sự thay đổi trong cú pháp kotlin. và nên mở khóa ofClass.enclosingClass.kotlin.objectInstance?.javaClassthay vìofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
à, đừng bận tâm, như đã nói ở đây kotlinlang.org/docs/reference/reflection.html bình phản chiếu được vận chuyển riêng từ stdlib, đối với lớp chúng ta cần điều này:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoàng Trần

1
Mã để tạo 'Đại biểu tài sản' và 'Hàm mở rộng' có vẻ giống nhau ngoại trừ loại trả về. Mẫu mã cho Thuộc tính Đại diện ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) xuất hiện để tạo một hàm mở rộng sao cho "".logger()bây giờ là một thứ, điều này có được cho là hành xử theo cách này không?
Mike Rylander

32

Có một cái nhìn vào thư viện đăng nhập kotlin .
Nó cho phép đăng nhập như thế:

private val logger = KotlinLogging.logger {}

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

Hay như thế:

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

Tôi cũng đã viết một bài đăng trên blog so sánh nó với AnkoLogger: Đăng nhập vào Kotlin & Android: AnkoLogger vs kotlin-log

Tuyên bố miễn trừ trách nhiệm: Tôi là người duy trì thư viện đó.

Chỉnh sửa: kotlin-log hiện có hỗ trợ đa nền tảng: https://github.com/MicroUtils/kotlin-logging/wiki/Multipl platform-support


Tôi có thể đề nghị bạn chỉnh sửa câu trả lời của mình để hiển thị đầu ra của các logger.info()cuộc gọi, như Jayson đã làm trong câu trả lời được chấp nhận của anh ấy.
Paulo Merson

7

Như một ví dụ điển hình về việc thực hiện ghi nhật ký, tôi muốn đề cập đến Anko sử dụng giao diện đặc biệt AnkoLoggermà một lớp cần ghi nhật ký nên thực hiện. Bên trong giao diện có mã tạo thẻ ghi nhật ký cho lớp. Ghi nhật ký sau đó được thực hiện thông qua các hàm mở rộng có thể được gọi bên trong triển khai xen kẽ mà không cần tiền tố hoặc thậm chí tạo cá thể logger.

Tôi không nghĩ đây là thành ngữ , nhưng có vẻ là một cách tiếp cận tốt vì nó yêu cầu mã tối thiểu, chỉ cần thêm giao diện vào khai báo lớp và bạn có thể đăng nhập bằng các thẻ khác nhau cho các lớp khác nhau.


Mã dưới đây về cơ bản là AnkoLogger , được đơn giản hóa và viết lại để sử dụng theo thuyết bất khả tri của Android.

Đầu tiên, có một giao diện hoạt động giống như giao diện đánh dấu:

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

Nó cho phép thực hiện nó sử dụng các hàm mở rộng cho MyLoggerbên trong mã của chúng chỉ bằng cách gọi chúng trên this. Và nó cũng chứa thẻ đăng nhập.

Tiếp theo, có một điểm vào chung cho các phương thức ghi nhật ký khác nhau:

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

Nó sẽ được gọi bằng phương pháp đăng nhập. Nó nhận được một thẻ từ MyLoggerthực hiện, kiểm tra cài đặt ghi nhật ký và sau đó gọi một trong hai trình xử lý, một trong Throwablesố đó có đối số và một không có.

Sau đó, bạn có thể xác định bao nhiêu phương thức đăng nhập tùy thích, theo cách này:

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

Chúng được xác định một lần cho cả việc ghi nhật ký chỉ là một tin nhắn và đăng nhập Throwablecũng vậy, điều này được thực hiện với throwabletham số tùy chọn .

Các hàm được truyền như handlerthrowableHandlercó thể khác nhau cho các phương thức ghi nhật ký khác nhau, ví dụ, chúng có thể ghi nhật ký vào tệp hoặc tải lên ở đâu đó. isLoggingEnabledLoggingLevelsđược bỏ qua cho ngắn gọn, nhưng sử dụng chúng cung cấp sự linh hoạt hơn nữa.


Nó cho phép sử dụng như sau:

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

Có một nhược điểm nhỏ: sẽ cần một đối tượng logger để đăng nhập vào các hàm cấp gói:

private object MyPackageLog : MyLogger

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

Câu trả lời này là dành riêng cho Android và câu hỏi không đề cập cũng như không có thẻ Android.
Jayson Minard

@JaysonMinard tại sao vậy? Cách tiếp cận này là mục đích chung vì, ví dụ, có một thẻ ghi nhật ký duy nhất cho mỗi lớp cũng hữu ích trong các dự án không phải Android.
phím nóng

1
Không rõ bạn đang nói "thực hiện một cái gì đó tương tự như những gì Anko đã làm" và thay vào đó có vẻ giống như "sử dụng Anko" ... sau đó yêu cầu một thư viện Android có tên là Anko. Trong đó có một giao diện có chức năng mở rộng gọi android.util.Logđể thực hiện đăng nhập. Đó là ý định của bạn? dùng Anko? Xây dựng một cái gì đó tương tự trong khi sử dụng Anko làm ví dụ (sẽ tốt hơn nếu bạn chỉ đặt mã được đề xuất nội tuyến và sửa nó cho không phải Android thay vì nói "cổng này sang không phải Android, đây là liên kết". Thay vào đó, bạn thêm mã mẫu gọi Anko)
Jayson Minard

1
@JaysonMinard, cảm ơn vì những bình luận của bạn, tôi đã viết lại bài đăng để giờ đây nó giải thích cách tiếp cận hơn là tham khảo Anko.
phím nóng

6

KISS: Dành cho các nhóm Java Di chuyển đến Kotlin

Nếu bạn không ngại cung cấp tên lớp trên mỗi lần khởi động của trình ghi nhật ký (giống như java), bạn có thể giữ cho nó đơn giản bằng cách định nghĩa đây là một hàm cấp cao nhất ở đâu đó trong dự án của bạn:

import org.slf4j.LoggerFactory

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

Điều này sử dụng một tham số loại thống nhất Kotlin .

Bây giờ, bạn có thể sử dụng điều này như sau:

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

Cách tiếp cận này là siêu đơn giản và gần với tương đương java, nhưng chỉ cần thêm một số đường cú pháp.

Bước tiếp theo: Tiện ích mở rộng hoặc Đại biểu

Cá nhân tôi thích tiến thêm một bước và sử dụng phương pháp mở rộng hoặc đại biểu. Điều này được tóm tắt độc đáo trong câu trả lời của @ JaysonMinard, nhưng đây là TL; DR cho cách tiếp cận "Đại biểu" với API log4j2 ( CẬP NHẬT : không cần phải viết mã này theo cách thủ công nữa, vì nó đã được phát hành dưới dạng mô-đun chính thức của dự án log4j2, xem bên dưới). Vì log4j2, không giống như slf4j, hỗ trợ đăng nhập bằng Supplier, tôi cũng đã thêm một đại biểu để sử dụng các phương thức này đơn giản hơn.

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 API đăng nhập Kotlin

Hầu hết các phần trước đã được điều chỉnh trực tiếp để tạo mô-đun API ghi nhật ký Kotlin , hiện là phần chính thức của Log4j2 (từ chối trách nhiệm: Tôi là tác giả chính). Bạn có thể tải xuống cái này trực tiếp từ Apache hoặc thông qua Maven Central .

Cách sử dụng về cơ bản như mô tả ở trên, nhưng mô-đun hỗ trợ cả truy cập logger dựa trên giao diện, loggerchức năng mở rộng Anyđể sử dụng ở nơi thisđược xác định và chức năng logger có tên để sử dụng khi không cóthis được xác định (chẳng hạn như các hàm cấp cao nhất).


1
Nếu tôi đúng, bạn có thể tránh gõ tên lớp trong giải pháp đầu tiên bạn cung cấp bằng cách thay đổi chữ ký phương thức thành T.logger ()
IPat

1
@IPat yup, giải pháp đầu tiên cố ý không làm điều đó để gần với "cách java". Phần thứ hai của câu trả lời bao gồm trường hợp mở rộng T.logger()- xem phần dưới cùng của mẫu mã.
Raman

5

Anko

Bạn có thể sử dụng Ankothư viện để làm điều đó. Bạn sẽ có mã như dưới đây:

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

đăng nhập kotlin

thư viện kotlin-log ( dự án Github - kotlin-log ) cho phép bạn viết mã đăng nhập như dưới đây:

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

Nhật ký tĩnh

hoặc bạn cũng có thể sử dụng chữ viết nhỏ này trong thư viện Kotlin được gọi StaticLogthì mã của bạn sẽ như sau:

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")\

Giải pháp thứ hai có thể tốt hơn nếu bạn muốn xác định định dạng đầu ra cho phương thức ghi nhật ký như:

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

hoặc sử dụng các bộ lọc, ví dụ:

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

woodkt

Nếu bạn đã sử dụng Timberkiểm tra thư viện đăng nhập của Jake Wharton timberkt.

Thư viện này được xây dựng trên Timber với API dễ sử dụng hơn từ Kotlin. Thay vì sử dụng các tham số định dạng, bạn chuyển một lambda chỉ được đánh giá nếu thông điệp được ghi lại.

Mã ví dụ:

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

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

Kiểm tra thêm: Đăng nhập vào Kotlin & Android: AnkoLogger vs kotlin-log

Hy vọng nó sẽ giúp


4

Một cái gì đó như thế này sẽ làm việc cho bạn?

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
Câu trả lời này cần giải thích nhiều hơn, nếu người hỏi không hiểu các đối tượng đồng hành, có lẽ họ đã không nhận được đại biểu, và do đó sẽ không biết điều này đang làm gì. Thêm vào đó có rất ít tiết kiệm mã sử dụng mô hình này. Và tôi nghi ngờ bộ nhớ đệm trong đối tượng đồng hành thực sự là một hiệu suất đạt được ngoài một hệ thống bị hạn chế với CPU nhỏ như Android.
Jayson Minard

1
Những gì đoạn mã trên đang thể hiện là việc tạo ra một lớp hoạt động như một Đại biểu (xem kotlinlang.org/docs/reference/delegated-properIES.html ) là lớp đầu tiên LoggerDelegate Và sau đó, nó đang tạo ra một hàm cấp cao nhất đang tạo ra dễ dàng hơn để tạo một thể hiện của đại biểu (không dễ hơn nhiều, nhưng một chút). Và chức năng đó nên được thay đổi để được inline. Sau đó, nó sử dụng đại biểu để cung cấp một logger bất cứ khi nào muốn. Nhưng nó cung cấp một cho bạn đồng hành Foo.Companionvà không phải cho lớp Foonên có thể không như dự định.
Jayson Minard

@JaysonMinard Tôi đồng ý nhưng tôi sẽ để lại câu trả lời cho những người xem trong tương lai muốn có "cách khắc phục nhanh" hoặc một ví dụ về cách áp dụng điều này cho các dự án của riêng họ. Tôi không hiểu tại sao logger()chức năng nên có inlinenếu không có lambdas. IntelliJ đề xuất nội tuyến trong trường hợp này là không cần thiết: i.imgur.com/YQH3NB1.png
Jire

1
Tôi đã kết hợp câu trả lời của bạn vào câu hỏi của tôi và đơn giản hóa nó bằng cách loại bỏ lớp đại biểu tùy chỉnh và sử dụng một trình bao bọc xung quanh Lazy. Với một mẹo để có được nó để biết nó thuộc lớp nào.
Jayson Minard

1

Tôi đã nghe nói không có thành ngữ về vấn đề này. Càng đơn giản càng tốt, vì vậy tôi sẽ sử dụng một thuộc tính cấp cao nhất

val logger = Logger.getLogger("package_name")

Cách thực hành này phục vụ tốt cho Python và khác với Kotlin và Python có thể xuất hiện, tôi tin rằng chúng khá giống nhau về "tinh thần" (nói về thành ngữ).


Cấp cao nhất còn được gọi là cấp gói.
Caelum

Một biến cấp cao nhất giống như nói "sử dụng biến toàn cục" và tôi nghĩ sẽ chỉ áp dụng được nếu bạn có các hàm cấp cao nhất khác cần sử dụng bộ ghi. Tuy nhiên, tại thời điểm đó, có thể tốt hơn để chuyển một logger đến bất kỳ chức năng tiện ích nào muốn đăng nhập.
Jayson Minard

1
@JaysonMinard Tôi nghĩ rằng việc chuyển logger làm tham số sẽ là một kiểu chống, bởi vì việc ghi nhật ký của bạn sẽ không bao giờ ảnh hưởng đến API, bên ngoài hoặc bên trong
voddan

Ok, sau đó trở lại quan điểm của tôi, để ghi nhật ký cấp lớp, hãy đặt logger vào lớp, không phải là hàm cấp cao nhất.
Jayson Minard

1
@voddan ít nhất cung cấp một ví dụ đầy đủ về loại logger mà bạn đang tạo. val log = what?!? ... Tạo một logger theo tên? Bỏ qua thực tế câu hỏi cho thấy anh ta muốn tạo một logger cho một lớp cụ thểLoggerFactory.getLogger(Foo.class);
Jayson Minard

1

Còn về chức năng mở rộng trên Class thì sao? Bằng cách đó, bạn kết thúc với:

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

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

Lưu ý - Tôi chưa từng thử nghiệm điều này, vì vậy nó có thể không hoàn toàn đúng.


1

Đầu tiên, bạn có thể thêm các chức năng mở rộng để tạo logger.

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

Sau đó, bạn sẽ có thể tạo một logger bằng cách sử dụng mã sau đây.

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

Thứ hai, bạn có thể định nghĩa một giao diện cung cấp bộ ghi chép và triển khai mixin của nó.

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)

Giao diện này có thể được sử dụng theo cách sau.

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

1

tạo đối tượng đồng hành và đánh dấu các trường thích hợp bằng chú thích @JvmStatic


1

Có rất nhiều câu trả lời tuyệt vời ở đây, nhưng tất cả chúng đều quan tâm đến việc thêm một logger vào một lớp, nhưng bạn sẽ làm thế nào để đăng nhập vào các hàm cấp cao nhất?

Cách tiếp cận này là chung chung và đủ đơn giản để hoạt động tốt trong cả hai lớp, đối tượng đồng hành và Hàm cấp cao nhất:

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

Nói chung, đó là những gì các đối tượng đồng hành dành cho: thay thế các công cụ tĩnh.


Một đối tượng đồng hành không phải là tĩnh, nó là một đơn vị có thể giữ các thành viên có thể trở thành tĩnh nếu bạn sử dụng JvmStaticchú thích. Và trong tương lai có thể có nhiều hơn một cho phép. Ngoài ra, câu trả lời này không hữu ích nếu không có thêm thông tin hoặc mẫu.
Jayson Minard

Tôi không nói đó là một động tĩnh. Tôi nói nó là để thay thế statics. Và tại sao sẽ có nhiều hơn một cho phép? Điều đó không có ý nghĩa. Cuối cùng, tôi đã vội vàng, và tôi nghĩ rằng việc chỉ đúng hướng sẽ đủ hữu ích.
Jacob Zimmerman

1
Một đối tượng đồng hành không phải để thay thế trạng thái, nhưng nó cũng có thể làm cho các yếu tố của nó tĩnh. Kotlin đã hỗ trợ nhiều hơn là đồng hành trong một thời gian và cho phép họ có các tên khác. Một khi bạn bắt đầu đặt tên cho chúng, chúng hoạt động ít giống như thống kê. Và nó được bỏ ngỏ trong tương lai để có nhiều hơn một người bạn đồng hành được đặt tên. Ví dụ: người này có thể Factoryvà người khácHelpers
Jayson Minard

0

Ví dụ Slf4j, tương tự cho những người khác. Điều này thậm chí hoạt động để tạo logger cấp gói

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

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

Sử dụng:

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

Đây vẫn là WIP (gần như đã hoàn thành) vì vậy tôi muốn chia sẻ nó: https://github.com/leandronunes85/log-format-enforcer#kotlin-ton-to-come-in-version-14

Mục tiêu chính của thư viện này là thực thi một kiểu nhật ký nhất định trong một dự án. Bằng cách tạo ra mã Kotlin, tôi đang cố gắng giải quyết một số vấn đề được đề cập trong câu hỏi này. Liên quan đến câu hỏi ban đầu, điều tôi thường làm chỉ đơn giản là:

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

}

0

Bạn chỉ có thể xây dựng "thư viện" tiện ích của riêng mình. Bạn không cần một thư viện lớn cho nhiệm vụ này sẽ làm cho dự án của bạn nặng nề và phức tạp hơn.

Chẳng hạn, bạn có thể sử dụng Phản xạ Kotlin để lấy tên, loại và giá trị của bất kỳ thuộc tính lớp nào.

Trước hết, hãy đảm bảo rằng bạn đã giải quyết được sự phụ thuộc meta trong bản dựng của mình.

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

Sau đó, bạn có thể chỉ cần sao chép và dán mã này vào dự án của mình:

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

Ví dụ về cách sử dụng:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.