У Kotlin нет такого же понятия статических полей, как в Java. В Java общепринятым способом ведения журнала является:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
Вопрос в том, каков идиоматический способ ведения логов в Kotlin?
kotlin
kotlin-logging
mchlstckl
источник
источник
Any
(таким образом, нуждающимся в приведении)?this.javaClass
для каждого. Но я не рекомендую это как решение.Ответы:
В большинстве зрелых кодов Kotlin вы найдете один из этих шаблонов ниже. Подход, использующий Property Delegates, использует возможности Kotlin для создания наименьшего кода.
Примечание: код здесь для,
java.util.Logging
но та же теория применима к любой библиотеке журналовСтатический (обычный, эквивалент вашего Java-кода в вопросе)
Если вы не можете доверять производительности этого поиска по хешу в системе ведения журналов, вы можете получить поведение, подобное вашему Java-коду, используя объект-компаньон, который может содержать экземпляр и чувствовать себя статичным для вас.
создание вывода:
Подробнее о сопутствующих объектах здесь: Companion Objects ... Также обратите внимание, что в приведенном выше примере
MyClass::class.java
получает экземпляр типаClass<MyClass>
для регистратора, в то время какthis.javaClass
получает экземпляр типаClass<MyClass.Companion>
.На экземпляр класса (общий)
Но на самом деле нет причин избегать звонков и получения регистратора на уровне экземпляра. Упомянутый вами идиоматический способ Java устарел и основан на страхе перед производительностью, тогда как регистратор для каждого класса уже кэшируется практически любой разумной системой журналирования на планете. Просто создайте член для хранения объекта регистратора.
создание вывода:
Вы можете проверить производительность как для каждого экземпляра, так и для каждого класса, и посмотреть, есть ли реальная разница для большинства приложений.
Имущественные делегаты (обычные, самые элегантные)
Другой подход, предложенный @Jire в другом ответе, заключается в создании делегата свойства, который затем можно использовать для равномерного выполнения логики в любом другом классе, который вы хотите. Есть более простой способ сделать это, поскольку Kotlin уже предоставляет
Lazy
делегата, мы можем просто обернуть его в функцию. Здесь есть одна хитрость: если мы хотим узнать тип класса, использующего в настоящее время делегат, мы сделаем его функцией расширения для любого класса:Этот код также гарантирует, что если вы используете его в объекте-компаньоне, имя регистратора будет таким же, как если бы вы использовали его в самом классе. Теперь вы можете просто:
для каждого экземпляра класса или если вы хотите, чтобы он был более статичным с одним экземпляром для каждого класса:
И ваш результат вызова
foo()
из обоих этих классов будет:Функции расширения (необычные в этом случае из-за «загрязнения» любого пространства имен)
У Kotlin есть несколько скрытых трюков, которые позволяют сделать этот код еще меньше. Вы можете создавать функции расширения для классов и, следовательно, предоставлять им дополнительную функциональность. Одним из предложений в комментариях выше было расширение
Any
с помощью функции регистрации. Это может создавать шум в любое время, когда кто-либо использует завершение кода в своей среде IDE в любом классе. Но есть секретное преимущество расширенияAny
или какого-либо другого интерфейса маркера: вы можете подразумевать, что вы расширяете свой собственный класс и, следовательно, обнаруживаете класс, в котором находитесь. А? Чтобы быть менее запутанным, вот код:Теперь внутри класса (или объекта-компаньона) я могу просто вызвать это расширение в своем собственном классе:
Производить продукцию:
По сути, код рассматривается как призыв к расширению
Something.logger()
. Проблема заключается в том, что следующее также может быть причиной «загрязнения» для других классов:Функции расширения в интерфейсе маркера (не уверен, насколько распространена, но распространена модель для «черт»)
Чтобы сделать использование расширений более понятным и уменьшить «загрязнение», вы можете использовать интерфейс маркера для расширения:
Или даже сделайте метод частью интерфейса с реализацией по умолчанию:
И используйте любой из этих вариантов в вашем классе:
Производить продукцию:
Если вы хотите принудительно создать единое поле для хранения регистратора, то при использовании этого интерфейса вы можете легко потребовать, чтобы у разработчика было поле, такое как
LOG
:Теперь разработчик интерфейса должен выглядеть так:
Конечно, абстрактный базовый класс может делать то же самое, имея опцию как интерфейса, так и абстрактного класса, реализующего этот интерфейс, что обеспечивает гибкость и единообразие:
Собираем все вместе (небольшая вспомогательная библиотека)
Вот небольшая вспомогательная библиотека, позволяющая легко использовать любой из перечисленных выше вариантов. В Kotlin принято расширять API-интерфейсы, чтобы сделать их по своему вкусу. Либо в расширении, либо в функциях верхнего уровня. Вот микс, чтобы дать вам варианты создания регистраторов, и пример, показывающий все варианты:
Выберите тот, который вы хотите сохранить, и вот все варианты:
Все 13 экземпляров регистраторов, созданных в этом образце, будут производить одно и то же имя регистратора и выводить:
Примечание: В
unwrapCompanionClass()
метод гарантирует , что мы не генерируют регистратор имени объекта - компаньона , а скорее объемлющего класса. В настоящее время это рекомендуемый способ найти класс, содержащий объект-компаньон. Удаление « $ Companion » из имени с помощьюremoveSuffix()
не работает, поскольку объектам-компаньонам могут быть заданы собственные имена.источник
ofClass.enclosingClass.kotlin.objectInstance?.javaClass
вместо этого должна бытьofClass.enclosingClass.kotlin.companionObject?.java
compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}
), по-видимому, создает такую функцию расширения, которая"".logger()
теперь является чем-то особенным. Должно ли это вести себя так?Взгляните на библиотеку kotlin-logging .
Это позволяет регистрироваться так:
Или вот так:
Я также написал сообщение в блоге, сравнивая его с
AnkoLogger
: Вход в Kotlin & Android: AnkoLogger vs kotlin-loggingОтказ от ответственности: я хранитель этой библиотеки.
Изменить: kotlin-logging теперь имеет многоплатформенную поддержку: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
источник
logger.info()
вызовов, как это сделал Джейсон в его общепринятом ответ.В качестве хорошего примера реализации журналирования я хотел бы упомянуть Anko, который использует специальный интерфейс,
AnkoLogger
который должен реализовать класс, который нуждается в журналировании. Внутри интерфейса есть код, который генерирует тег регистрации для класса. Ведение журнала затем осуществляется с помощью функций расширения, которые могут вызываться внутри реализации взаимодействия, без префиксов или даже создания экземпляра регистратора.Я не думаю, что это идиоматично , но это кажется хорошим подходом, так как требует минимального кода, просто добавив интерфейс к объявлению класса, и вы получите логирование с разными тегами для разных классов.
Код ниже в основном AnkoLogger , упрощенный и переписанный для использования без Android.
Во-первых, есть интерфейс, который ведет себя как интерфейс маркера:
Это позволяет его реализации использовать функции расширений
MyLogger
внутри их кода, просто вызывая ихthis
. И он также содержит тег регистрации.Далее, есть общая точка входа для различных методов регистрации:
Это будет вызвано методами регистрации. Он получает тег из
MyLogger
реализации, проверяет параметры ведения журнала и затем вызывает один из двух обработчиков, один сThrowable
аргументом, а другой без.Затем вы можете определить сколько угодно методов ведения журнала следующим образом:
Они определяются один раз как для регистрации только сообщения, так и для регистрации
Throwable
, это делается с помощью необязательногоthrowable
параметра.Функции, которые передаются как
handler
иthrowableHandler
могут отличаться для разных методов ведения журнала, например, они могут записать журнал в файл или загрузить его куда-нибудь.isLoggingEnabled
иLoggingLevels
опущены для краткости, но их использование обеспечивает еще большую гибкость.Это позволяет для следующего использования:
Есть небольшой недостаток: для входа в функции уровня пакета потребуется объект регистратора:
источник
android.util.Log
для ведения журнала. Каковы были ваши намерения? использовать анко? Собрать нечто подобное, используя в качестве примера Anko (лучше просто вставить предложенный код в строку и исправить его для не Android, а вместо того, чтобы сказать «перенести это на не Android, вот ссылка». Вместо этого вы добавляете пример кода звонит Анко)KISS: для команд Java, мигрирующих в Котлин
Если вы не возражаете предоставить имя класса в каждом экземпляре логгера (так же, как в java), вы можете упростить его, определив его как функцию верхнего уровня где-то в вашем проекте:
При этом используется параметр типа Kotlin reified .
Теперь вы можете использовать это следующим образом:
Этот подход очень прост и близок к Java-эквиваленту, но добавляет немного синтаксического сахара.
Следующий шаг: расширения или делегаты
Лично я предпочитаю идти дальше и использовать подход расширений или делегатов. Это приятно обобщено в ответе @ JaysonMinard, но вот TL; DR для подхода «Делегировать» с API log4j2 ( ОБНОВЛЕНИЕ : больше не нужно писать этот код вручную, так как он был выпущен как официальный модуль проект log4j2, см. ниже). Поскольку log4j2, в отличие от slf4j, поддерживает ведение журнала с помощью
Supplier
s, я также добавил делегата, чтобы упростить использование этих методов.Log4j2 Kotlin Logging API
Большая часть предыдущего раздела была непосредственно адаптирована для создания модуля API Kotlin Logging , который теперь является официальной частью Log4j2 (отказ от ответственности: я являюсь основным автором). Вы можете скачать его прямо из Apache или через Maven Central .
Использование в основном такое, как описано выше, но модуль поддерживает как интерфейсный доступ к регистратору, функцию
logger
расширенияAny
для использования там, гдеthis
она определена, так и именованную функцию регистратора для использования там, где неthis
определено (например, функции верхнего уровня).источник
T.logger()
- см. Нижнюю часть примера кода.Анко
Вы можете использовать
Anko
библиотеку, чтобы сделать это. У вас будет код, как показано ниже:Котлин-каротаж
Библиотека kotlin-logging ( проект Github - kotlin-logging ) позволяет писать код регистрации, как показано ниже:
StaticLog
или вы можете также использовать эту небольшую написанную в Kotlin библиотеку, называемую
StaticLog
тогда, ваш код будет выглядеть так:Второе решение может быть лучше, если вы хотите определить выходной формат для метода ведения журнала, например:
или используйте фильтры, например:
timberkt
Если вы уже воспользовались
Timber
проверкой библиотеки журналов Джейка Уортонаtimberkt
.Пример кода:
Проверьте также: Вход в Kotlin & Android: AnkoLogger vs kotlin-logging
Надеюсь, это поможет
источник
Будет ли что-то подобное для вас?
источник
LoggerDelegate
а затем он создает функцию верхнего уровня, которая создает проще создать экземпляр делегата (не намного проще, но немного). И эта функция должна быть изменена, чтобы бытьinline
. Затем он использует делегата для предоставления регистратора всякий раз, когда он требуется. Но он предоставляет один для компаньона,Foo.Companion
а не для класса,Foo
так что, возможно, не так, как задумано.logger()
функция должна быть,inline
если нет лямбды. IntelliJ предлагает встраивание в этом случае не требуется: i.imgur.com/YQH3NB1.pngLazy
вместо этого использовал оболочку . С трюком, чтобы узнать, в каком классе он находится.Я не слышал об идиомах в этом отношении. Чем проще, тем лучше, поэтому я бы использовал свойство верхнего уровня
Эта практика хорошо работает в Python, и, как бы ни выглядели Kotlin и Python, я думаю, что они очень похожи в «духе» (говоря об идиомах).
источник
val log = what?!?
... создать регистратор по имени? Игнорируя тот факт, что вопрос показал, что он хотел создать регистратор для определенного классаLoggerFactory.getLogger(Foo.class);
А как насчет функции расширения вместо Class? Таким образом, вы получите:
Примечание: я вообще не проверял это, так что это может быть не совсем правильно.
источник
Во-первых, вы можете добавить функции расширения для создания регистратора.
Тогда вы сможете создать регистратор, используя следующий код.
Во-вторых, вы можете определить интерфейс, который обеспечивает регистратор и его смешанную реализацию.
Этот интерфейс может быть использован следующим образом.
источник
создать сопутствующий объект и пометить соответствующие поля аннотацией @JvmStatic
источник
Здесь уже есть много хороших ответов, но все они касаются добавления регистратора в класс, но как бы вы это сделали, чтобы войти в функции верхнего уровня?
Этот подход является достаточно общим и простым, чтобы хорошо работать в обоих классах, сопутствующих объектах и функциях верхнего уровня:
источник
В общем, для этого нужны сопутствующие объекты: замена статического материала.
источник
JvmStatic
аннотацию. И в будущем их может быть больше одного. Плюс этот ответ не очень полезен без дополнительной информации или образца.Factory
а другойHelpers
Пример Slf4j, то же самое для других. Это даже работает для создания регистратора уровня пакета
Использование:
источник
источник
Это все еще WIP (почти закончен), поэтому я хотел бы поделиться им: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14
Основная цель этой библиотеки - обеспечить определенный стиль журнала для всего проекта. Имея это генерировать код Kotlin, я пытаюсь решить некоторые из проблем, упомянутых в этом вопросе. Что касается первоначального вопроса, то я обычно стремлюсь просто:
источник
Вы можете просто создать свою собственную «библиотеку» утилит. Для этой задачи вам не нужна большая библиотека, которая сделает ваш проект более тяжелым и сложным.
Например, вы можете использовать Kotlin Reflection, чтобы получить имя, тип и значение любого свойства класса.
Прежде всего, убедитесь, что у вас есть мета-зависимость, установленная в вашем build.gradle:
После этого вы можете просто скопировать и вставить этот код в ваш проект:
Пример использования:
источник