Kotlin hat nicht die gleiche Vorstellung von statischen Feldern wie in Java. In Java ist die allgemein akzeptierte Methode zur Protokollierung:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
Die Frage ist, wie die Protokollierung in Kotlin idiomatisch durchgeführt wird.
kotlin
kotlin-logging
mchlstckl
quelle
quelle
Any
(also eine Besetzung erforderlich)?this.javaClass
für jeden zurück. Aber ich empfehle es nicht als Lösung.Antworten:
In den meisten ausgereiften Kotlin-Codes finden Sie unten eines dieser Muster. Der Ansatz mit Property Delegates nutzt die Leistungsfähigkeit von Kotlin, um den kleinsten Code zu erstellen.
Hinweis: Der Code hier ist für,
java.util.Logging
aber die gleiche Theorie gilt für jede ProtokollierungsbibliothekStatisch (häufig, entspricht Ihrem Java-Code in der Frage)
Wenn Sie der Leistung dieser Hash-Suche im Protokollierungssystem nicht vertrauen können, können Sie ein ähnliches Verhalten wie bei Ihrem Java-Code erzielen, indem Sie ein Begleitobjekt verwenden, das eine Instanz enthalten und sich für Sie statisch anfühlen kann.
Ausgabe erstellen:
Mehr über Begleiter Objekte hier: Companion Objekte ... Auch , dass in der Probe beachten Sie über
MyClass::class.java
die Instanz des Typs wirdClass<MyClass>
für den Logger, währendthis.javaClass
die Instanz des Typs erhalten würdeClass<MyClass.Companion>
.Pro Instanz einer Klasse (gemeinsam)
Es gibt jedoch keinen Grund, das Aufrufen und Abrufen eines Loggers auf Instanzebene zu vermeiden. Die von Ihnen erwähnte idiomatische Java-Methode ist veraltet und basiert auf Leistungsangst, während der Logger pro Klasse bereits von fast jedem vernünftigen Protokollierungssystem auf dem Planeten zwischengespeichert wird. Erstellen Sie einfach ein Mitglied, das das Logger-Objekt enthält.
Ausgabe erstellen:
Sie können Leistungstests sowohl pro Instanz als auch pro Klasse durchführen und feststellen, ob für die meisten Apps ein realistischer Unterschied besteht.
Property Delegates (gewöhnlich, am elegantesten)
Ein anderer Ansatz, der von @Jire in einer anderen Antwort vorgeschlagen wird, besteht darin, einen Eigenschaftendelegaten zu erstellen, mit dem Sie die Logik in jeder anderen gewünschten Klasse einheitlich ausführen können. Es gibt einen einfacheren Weg, dies zu tun, da Kotlin
Lazy
bereits einen Delegaten bereitstellt . Wir können ihn einfach in eine Funktion einschließen. Ein Trick dabei ist, dass wir, wenn wir den Typ der Klasse kennen möchten, die derzeit den Delegaten verwendet, ihn zu einer Erweiterungsfunktion für jede Klasse machen:Dieser Code stellt außerdem sicher, dass bei Verwendung in einem Companion-Objekt der Loggername mit dem Namen übereinstimmt, den Sie für die Klasse selbst verwendet haben. Jetzt können Sie einfach:
für pro Klasseninstanz oder wenn Sie möchten, dass es mit einer Instanz pro Klasse statischer ist:
Und Ihre Ausgabe vom Aufrufen dieser
foo()
beiden Klassen wäre:Erweiterungsfunktionen (in diesem Fall aufgrund der "Verschmutzung" eines beliebigen Namespace ungewöhnlich)
Kotlin hat ein paar versteckte Tricks, mit denen Sie einen Teil dieses Codes noch kleiner machen können. Sie können Erweiterungsfunktionen für Klassen erstellen und ihnen daher zusätzliche Funktionen geben. Ein Vorschlag in den obigen Kommentaren war,
Any
mit einer Logger-Funktion zu erweitern. Dies kann jedes Mal zu Rauschen führen, wenn jemand in einer ID eine Code-Vervollständigung in seiner IDE verwendet. Das ErweiternAny
oder eine andere Markierungsschnittstelle hat jedoch einen geheimen Vorteil : Sie können implizieren, dass Sie Ihre eigene Klasse erweitern und somit die Klasse erkennen, in der Sie sich befinden. Huh? Um weniger verwirrend zu sein, hier ist der Code:Jetzt kann ich innerhalb einer Klasse (oder eines Begleitobjekts) diese Erweiterung einfach für meine eigene Klasse aufrufen:
Ausgabe produzieren:
Grundsätzlich wird der Code als Aufruf zur Erweiterung angesehen
Something.logger()
. Das Problem ist, dass Folgendes auch zutreffen könnte, was zu einer "Verschmutzung" anderer Klassen führt:Erweiterungsfunktionen auf der Marker-Schnittstelle (nicht sicher, wie häufig, aber gemeinsames Modell für "Merkmale")
Um die Verwendung von Erweiterungen sauberer zu gestalten und die "Verschmutzung" zu verringern, können Sie eine Markierungsschnittstelle verwenden, um Folgendes zu erweitern:
Oder machen Sie die Methode mit einer Standardimplementierung zu einem Teil der Schnittstelle:
Und verwenden Sie eine dieser Variationen in Ihrer Klasse:
Ausgabe produzieren:
Wenn Sie die Schaffung eines einheitlichen Feldes zwingen wollte , den Logger zu halten, dann , während Sie diese Schnittstelle können Sie bequem die Implementierer erfordern ein Feld zu haben , wie zum Beispiel
LOG
:Jetzt muss der Implementierer der Schnittstelle folgendermaßen aussehen:
Natürlich kann eine abstrakte Basisklasse dasselbe tun, wobei die Option, dass sowohl die Schnittstelle als auch eine abstrakte Klasse, die diese Schnittstelle implementiert, Flexibilität und Einheitlichkeit ermöglichen:
Alles zusammenfügen (Eine kleine Hilfsbibliothek)
Hier ist eine kleine Hilfsbibliothek, mit der Sie die oben genannten Optionen einfach verwenden können. In Kotlin ist es üblich, APIs zu erweitern, um sie Ihren Wünschen anzupassen. Entweder in Erweiterungs- oder Top-Level-Funktionen. Hier finden Sie eine Mischung mit Optionen zum Erstellen von Loggern sowie ein Beispiel mit allen Variationen:
Wählen Sie die Optionen aus, die Sie behalten möchten, und hier sind alle verwendeten Optionen aufgeführt:
Alle 13 Instanzen der in diesem Beispiel erstellten Logger erzeugen denselben Loggernamen und dieselbe Ausgabe:
Hinweis: Die
unwrapCompanionClass()
Methode stellt sicher, dass kein Logger generiert wird, der nach dem Begleitobjekt benannt ist, sondern nach der einschließenden Klasse. Dies ist die derzeit empfohlene Methode, um die Klasse zu finden, die das Begleitobjekt enthält. Das Entfernen von " $ Companion " aus dem Namen mitremoveSuffix()
funktioniert nicht, da Companion-Objekte benutzerdefinierte Namen erhalten können.quelle
ofClass.enclosingClass.kotlin.objectInstance?.javaClass
stattofClass.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)}}
) scheint eine Erweiterungsfunktion zu erstellen,"".logger()
die nun eine Sache ist. Soll sich dies so verhalten?Schauen Sie sich die Kotlin-Logging- Bibliothek an.
Es ermöglicht die Protokollierung wie folgt:
Oder so:
Ich schrieb auch einen Beitrag Blog es zu vergleichen
AnkoLogger
: Logging in Kotlin & Android: AnkoLogger vs Kotlin-loggingHaftungsausschluss: Ich bin der Betreuer dieser Bibliothek.
Bearbeiten: kotlin-logging unterstützt jetzt mehrere Plattformen: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
quelle
logger.info()
Anrufen anzuzeigen, wie es Jayson in seiner akzeptierten Antwort getan hat.Als gutes Beispiel für die Implementierung der Protokollierung möchte ich Anko erwähnen , das eine spezielle Schnittstelle verwendet,
AnkoLogger
die eine Klasse, die protokolliert werden muss, implementieren sollte. In der Schnittstelle befindet sich Code, der ein Protokollierungs-Tag für die Klasse generiert. Die Protokollierung erfolgt dann über Erweiterungsfunktionen, die innerhalb der Interace-Implementierung ohne Präfixe oder sogar die Erstellung von Logger-Instanzen aufgerufen werden können.Ich denke nicht, dass dies idiomatisch ist , aber es scheint ein guter Ansatz zu sein, da es minimalen Code erfordert, nur die Schnittstelle zu einer Klassendeklaration hinzuzufügen und Sie mit verschiedenen Tags für verschiedene Klassen protokollieren.
Der folgende Code ist im Grunde AnkoLogger , vereinfacht und für die Android- unabhängige Verwendung neu geschrieben.
Erstens gibt es eine Schnittstelle, die sich wie eine Markierungsschnittstelle verhält:
Die Implementierung kann die Erweiterungsfunktionen für
MyLogger
den Code verwenden, der sie nur aufruftthis
. Und es enthält auch Protokollierungs-Tag.Als nächstes gibt es einen allgemeinen Einstiegspunkt für verschiedene Protokollierungsmethoden:
Es wird durch Protokollierungsmethoden aufgerufen. Es erhält ein Tag von der
MyLogger
Implementierung, überprüft die Protokollierungseinstellungen und ruft dann einen von zwei Handlern auf, den mitThrowable
und den ohne Argument.Anschließend können Sie so viele Protokollierungsmethoden definieren, wie Sie möchten:
Diese werden sowohl für die Protokollierung nur einer Nachricht
Throwable
als auch für die Protokollierung einer Nachricht einmal definiert. Dies erfolgt mit einem optionalenthrowable
Parameter.Die Funktionen, die als übergeben werden
handler
undthrowableHandler
für verschiedene Protokollierungsmethoden unterschiedlich sein können, können beispielsweise das Protokoll in eine Datei schreiben oder irgendwo hochladen.isLoggingEnabled
undLoggingLevels
werden der Kürze halber weggelassen, aber ihre Verwendung bietet noch mehr Flexibilität.Es ermöglicht die folgende Verwendung:
Es gibt einen kleinen Nachteil: Für die Anmeldung von Funktionen auf Paketebene wird ein Logger-Objekt benötigt:
quelle
android.util.Log
um die Protokollierung durchzuführen. Welches war deine Absicht? Anko benutzen? Erstellen Sie etwas Ähnliches, während Sie Anko als Beispiel verwenden (es ist besser, wenn Sie nur den vorgeschlagenen Code inline setzen und ihn für Nicht-Android korrigieren, anstatt zu sagen: "Portieren Sie dies auf Nicht-Android, hier ist der Link". Stattdessen fügen Sie Beispielcode hinzu Anko anrufen)KISS: Für Java-Teams, die nach Kotlin migrieren
Wenn es Ihnen nichts ausmacht, den Klassennamen bei jeder Instanziierung des Loggers anzugeben (genau wie bei Java), können Sie dies einfach halten, indem Sie dies als Funktion der obersten Ebene irgendwo in Ihrem Projekt definieren:
Dies verwendet einen Kotlin- Parameter vom Typ reified .
Jetzt können Sie dies wie folgt verwenden:
Dieser Ansatz ist sehr einfach und kommt dem Java-Äquivalent nahe, fügt jedoch nur etwas syntaktischen Zucker hinzu.
Nächster Schritt: Erweiterungen oder Delegaten
Ich persönlich gehe lieber einen Schritt weiter und verwende den Ansatz der Erweiterungen oder Delegierten. Dies ist in der Antwort von @ JaysonMinard gut zusammengefasst, aber hier ist der TL; DR für den "Delegate" -Ansatz mit der log4j2-API ( UPDATE : Sie müssen diesen Code nicht mehr manuell schreiben, da er als offizielles Modul des veröffentlicht wurde log4j2-Projekt, siehe unten). Da log4j2 im Gegensatz zu slf4j die Protokollierung mit
Supplier
's unterstützt, habe ich auch einen Delegaten hinzugefügt, um die Verwendung dieser Methoden zu vereinfachen.Log4j2 Kotlin-Protokollierungs-API
Der größte Teil des vorherigen Abschnitts wurde direkt angepasst, um das Kotlin Logging API- Modul zu erstellen , das jetzt offizieller Bestandteil von Log4j2 ist (Haftungsausschluss: Ich bin der Hauptautor). Sie können dies direkt von Apache oder über Maven Central herunterladen .
Die Verwendung erfolgt im Wesentlichen wie oben beschrieben, das Modul unterstützt jedoch sowohl den schnittstellenbasierten Loggerzugriff als auch eine
logger
ErweiterungsfunktionAny
für die Verwendung, bei derthis
definiert ist, und eine benannte Loggerfunktion für die Verwendung, bei der nothis
definiert ist (z. B. Funktionen der obersten Ebene).quelle
T.logger()
- siehe unten im Codebeispiel.Anko
Sie können die
Anko
Bibliothek verwenden, um dies zu tun. Sie hätten folgenden Code:Kotlin-Protokollierung
Mit der Bibliothek kotlin-logging ( Github-Projekt - kotlin-logging ) können Sie Protokollierungscode wie folgt schreiben:
StaticLog
oder Sie können auch diese kleine in Kotlin geschriebene Bibliothek verwenden, die heißt,
StaticLog
dann würde Ihr Code wie folgt aussehen:Die zweite Lösung ist möglicherweise besser, wenn Sie ein Ausgabeformat für die Protokollierungsmethode definieren möchten, z.
oder verwenden Sie Filter, zum Beispiel:
timberkt
Wenn Sie bereits Jake Whartons
Timber
Protokollierungsbibliotheksprüfung verwendet habentimberkt
.Codebeispiel:
Überprüfen Sie auch: Anmelden in Kotlin & Android: AnkoLogger vs Kotlin-Protokollierung
Hoffe es wird helfen
quelle
Würde so etwas für Sie funktionieren?
quelle
LoggerDelegate
. Anschließend wird eine Funktion der obersten Ebene erstellt Es ist einfacher, eine Instanz des Delegaten zu erstellen (nicht viel einfacher, aber ein wenig). Und diese Funktion sollte geändert werden, um zu seininline
. Anschließend wird der Delegat verwendet, um einen Logger bereitzustellen, wann immer dies gewünscht wird. Aber es bietet eine für den BegleiterFoo.Companion
und nicht für die Klasse,Foo
so ist es vielleicht nicht wie beabsichtigt.logger()
Funktion sein sollte,inline
wenn keine Lambdas vorhanden sind. IntelliJ schlägt vor, dass Inlining in diesem Fall nicht erforderlichLazy
stattdessen einen Wrapper verwendet habe. Mit einem Trick, um herauszufinden, in welcher Klasse es sich befindet.Ich habe diesbezüglich keine Redewendung gehört. Je einfacher desto besser, daher würde ich eine Eigenschaft der obersten Ebene verwenden
Diese Praxis eignet sich gut für Python, und so unterschiedlich Kotlin und Python auch erscheinen mögen, ich glaube, sie sind sich in ihrem "Geist" (wenn man von Redewendungen spricht) ziemlich ähnlich.
quelle
val log = what?!?
... einen Logger mit Namen erstellen? Ohne die Tatsache zu beachten, dass die Frage zeigte, dass er einen Logger für eine bestimmte Klasse erstellen wollteLoggerFactory.getLogger(Foo.class);
Was ist stattdessen mit einer Erweiterungsfunktion für Class? Auf diese Weise erhalten Sie:
Hinweis - Ich habe dies überhaupt nicht getestet, daher ist es möglicherweise nicht ganz richtig.
quelle
Zunächst können Sie Erweiterungsfunktionen für die Loggererstellung hinzufügen.
Anschließend können Sie mit dem folgenden Code einen Logger erstellen.
Zweitens können Sie eine Schnittstelle definieren, die einen Logger und dessen Mixin-Implementierung bereitstellt.
Diese Schnittstelle kann folgendermaßen verwendet werden.
quelle
Erstellen Sie ein Begleitobjekt und markieren Sie die entsprechenden Felder mit der Annotation @JvmStatic
quelle
Hier gibt es bereits viele gute Antworten, aber alle betreffen das Hinzufügen eines Loggers zu einer Klasse. Wie würden Sie dies tun, um sich in Funktionen der obersten Ebene anzumelden?
Dieser Ansatz ist allgemein und einfach genug, um sowohl in Klassen als auch in Begleitobjekten und Funktionen der obersten Ebene gut zu funktionieren:
quelle
Dafür sind Begleitobjekte im Allgemeinen gedacht: statisches Material ersetzen.
quelle
JvmStatic
Anmerkungen verwenden. Und in Zukunft ist möglicherweise mehr als eine zulässig. Außerdem ist diese Antwort ohne weitere Informationen oder ein Beispiel nicht sehr hilfreich.Factory
und ein andererHelpers
Slf4j Beispiel, dasselbe für andere. Dies funktioniert sogar zum Erstellen eines Loggers auf Paketebene
Verwendung:
quelle
quelle
Dies ist noch WIP (fast fertig), daher möchte ich es teilen: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14
Das Hauptziel dieser Bibliothek besteht darin, einen bestimmten Protokollstil in einem Projekt durchzusetzen. Indem ich Kotlin-Code generieren lasse, versuche ich, einige der in dieser Frage genannten Probleme zu lösen. In Bezug auf die ursprüngliche Frage neige ich normalerweise dazu, einfach:
quelle
Sie können einfach Ihre eigene "Bibliothek" von Dienstprogrammen erstellen. Für diese Aufgabe benötigen Sie keine große Bibliothek, wodurch Ihr Projekt schwerer und komplexer wird.
Sie können beispielsweise Kotlin Reflection verwenden, um den Namen, den Typ und den Wert einer Klasseneigenschaft abzurufen.
Stellen Sie zunächst sicher, dass die Meta-Abhängigkeit in Ihrem build.gradle festgelegt ist:
Anschließend können Sie diesen Code einfach kopieren und in Ihr Projekt einfügen:
Anwendungsbeispiel:
quelle