Skip to content

Commit

Permalink
Add logging module (#22)
Browse files Browse the repository at this point in the history
* Add JVM/JS logging module

* Dump API

* Codecov fix and top level readme

* Implement PR feedback:
- Various style fixes
- Make implicit tag generation pluggable for more efficient options
- Allow explicit tag use

* Fix lint failure

* Implement more PR feedback

* Lint fix
  • Loading branch information
cedrickcooke authored Dec 3, 2020
1 parent 8e77f2a commit ae28ac2
Show file tree
Hide file tree
Showing 24 changed files with 1,000 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Toolbox of utilities/helpers for Kotlin development.
![badge][badge-jvm]
![badge][badge-mac]

- [`logging`](logging) - Multiplatform logging library.
![badge][badge-js]
![badge][badge-jvm]

- [`functional`](functional) - Tools/utilities for manipulating functions
![badge][badge-js]
![badge][badge-jvm]
Expand Down
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ coverage:
fixes:
- "com/juul/tuulbox/collections::"
- "com/juul/tuulbox/coroutines/flow::"
- "com/juul/tuulbox/logging::"
- "com/juul/tuulbox/functional::"
60 changes: 60 additions & 0 deletions logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
![badge-js]
![badge-jvm]

# Logging

Tool box for a simple multiplatform logging API.

## Installation

### Gradle

Artifacts are hosted on GitHub packages, which can be configured as follows:

```kotlin
import java.net.URI

repositories {
maven {
url = URI("https://maven.pkg.github.com/juullabs/android-github-packages")
credentials {
username = findProperty("github.packages.username") as String
password = findProperty("github.packages.password") as String
}
}
}
```

Then the needed artifact(s) can be defined as dependencies.

**Multiplatform projects**

```kotlin
kotlin {
sourceSets {
val commonMain by getting {
implementation("com.juul.tuulbox:logging:$version")
}
}
}
```

**Platform-specific projects**

```kotlin
dependencies {
implementation("com.juul.tuulbox:logging-$platform:$version")
}
```


[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat
[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/platform-jvm-DB413D.svg?style=flat
[badge-linux]: http://img.shields.io/badge/platform-linux-2D3F6C.svg?style=flat
[badge-windows]: http://img.shields.io/badge/platform-windows-4D76CD.svg?style=flat
[badge-mac]: http://img.shields.io/badge/platform-macos-111111.svg?style=flat
[badge-watchos]: http://img.shields.io/badge/platform-watchos-C0C0C0.svg?style=flat
[badge-tvos]: http://img.shields.io/badge/platform-tvos-808080.svg?style=flat
[badge-wasm]: https://img.shields.io/badge/platform-wasm-624FE8.svg?style=flat
59 changes: 59 additions & 0 deletions logging/api/logging.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
public final class com/juul/tuulbox/logging/ConsoleLogger : com/juul/tuulbox/logging/Logger {
public static final field INSTANCE Lcom/juul/tuulbox/logging/ConsoleLogger;
public fun assert (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun debug (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun error (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun info (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun verbose (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun warn (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
}

public final class com/juul/tuulbox/logging/ConstantTagGenerator : com/juul/tuulbox/logging/TagGenerator {
public fun <init> (Ljava/lang/String;)V
public fun getTag ()Ljava/lang/String;
}

public final class com/juul/tuulbox/logging/DispatchLogger : com/juul/tuulbox/logging/Logger {
public fun <init> ()V
public fun assert (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public final fun clear ()V
public fun debug (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun error (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun info (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public final fun install (Lcom/juul/tuulbox/logging/Logger;)V
public fun verbose (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public fun warn (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
}

public final class com/juul/tuulbox/logging/Log {
public static final field INSTANCE Lcom/juul/tuulbox/logging/Log;
public final fun assert (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun assert$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
public final fun debug (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun debug$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
public final fun error (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun error$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
public final fun getDispatcher ()Lcom/juul/tuulbox/logging/DispatchLogger;
public final fun getTagGenerator ()Lcom/juul/tuulbox/logging/TagGenerator;
public final fun info (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun info$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
public final fun setTagGenerator (Lcom/juul/tuulbox/logging/TagGenerator;)V
public final fun verbose (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun verbose$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
public final fun warn (Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun warn$default (Lcom/juul/tuulbox/logging/Log;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
}

public abstract interface class com/juul/tuulbox/logging/Logger {
public abstract fun assert (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public abstract fun debug (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public abstract fun error (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public abstract fun info (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public abstract fun verbose (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
public abstract fun warn (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
}

public abstract interface class com/juul/tuulbox/logging/TagGenerator {
public abstract fun getTag ()Ljava/lang/String;
}

38 changes: 38 additions & 0 deletions logging/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
plugins {
kotlin("multiplatform")
id("org.jmailen.kotlinter")
jacoco
`maven-publish`
}

apply(from = rootProject.file("gradle/jacoco.gradle.kts"))
apply(from = rootProject.file("gradle/publish.gradle.kts"))

kotlin {
explicitApi()

jvm()
js().browser()

sourceSets {
val commonTest by getting {
dependencies {
implementation(project(":test"))
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}

val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}

val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
}
4 changes: 4 additions & 0 deletions logging/src/commonMain/kotlin/ConsoleLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.juul.tuulbox.logging

/** Logger for the console. */
public expect object ConsoleLogger : Logger
6 changes: 6 additions & 0 deletions logging/src/commonMain/kotlin/ConstantTagGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.juul.tuulbox.logging

/** Constant tag generator, for when performance is more important than useful implicit tags. */
public class ConstantTagGenerator(private val tag: String) : TagGenerator {
override fun getTag(): String = tag
}
44 changes: 44 additions & 0 deletions logging/src/commonMain/kotlin/DispatchLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.juul.tuulbox.logging

/** Implementation of [Logger] which dispatches calls to consumer [Logger]s. */
public class DispatchLogger : Logger {
private val consumers = mutableSetOf<Logger>()

/** `false` if no consumers have been installed, `true` if at least one consumer has been installed. */
internal val hasConsumers: Boolean
get() = consumers.isNotEmpty()

/** Add a consumer to receive future dispatch calls. */
public fun install(consumer: Logger) {
consumers.add(consumer)
}

/** Uninstall all installed consumers. */
public fun clear() {
consumers.clear()
}

override fun verbose(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.verbose(tag, message, throwable) }
}

override fun debug(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.debug(tag, message, throwable) }
}

override fun info(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.info(tag, message, throwable) }
}

override fun warn(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.warn(tag, message, throwable) }
}

override fun error(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.error(tag, message, throwable) }
}

override fun assert(tag: String, message: String, throwable: Throwable?) {
consumers.forEach { it.assert(tag, message, throwable) }
}
}
56 changes: 56 additions & 0 deletions logging/src/commonMain/kotlin/Log.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.juul.tuulbox.logging

import kotlin.jvm.Volatile

/** Global logging object. To receive logs, call [dispatcher].[install][DispatchLogger.install]. */
public object Log {

/** Global log dispatcher. */
public val dispatcher: DispatchLogger = DispatchLogger()

/** Global tag generator for log calls without explicit tag. */
@Volatile
public var tagGenerator: TagGenerator = defaultTagGenerator

/** Send a verbose-level log message to the global dispatcher. */
public fun verbose(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.verbose(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}

/** Send a debug-level log message to the global dispatcher. */
public fun debug(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.debug(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}

/** Send an info-level log message to the global dispatcher. */
public fun info(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.info(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}

/** Send an warn-level log message to the global dispatcher. */
public fun warn(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.warn(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}

/** Send an error-level log message to the global dispatcher. */
public fun error(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.error(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}

/** Send an assert-level log message to the global dispatcher. */
public fun assert(throwable: Throwable? = null, tag: String? = null, message: () -> String) {
if (dispatcher.hasConsumers) {
dispatcher.assert(tag ?: tagGenerator.getTag(), message.invoke(), throwable)
}
}
}
23 changes: 23 additions & 0 deletions logging/src/commonMain/kotlin/Logger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.juul.tuulbox.logging

/** Classes which implement [Logger] can write logs. */
public interface Logger {

/** Log at verbose-level. */
public fun verbose(tag: String, message: String, throwable: Throwable?)

/** Log at debug-level. */
public fun debug(tag: String, message: String, throwable: Throwable?)

/** Log at info-level. */
public fun info(tag: String, message: String, throwable: Throwable?)

/** Log at warn-level. */
public fun warn(tag: String, message: String, throwable: Throwable?)

/** Log at error-level. */
public fun error(tag: String, message: String, throwable: Throwable?)

/** Log at assert-level. */
public fun assert(tag: String, message: String, throwable: Throwable?)
}
10 changes: 10 additions & 0 deletions logging/src/commonMain/kotlin/TagGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.juul.tuulbox.logging

internal expect val defaultTagGenerator: TagGenerator

/** Creates tag strings for implicitly-tagged [Log] calls. */
public interface TagGenerator {

/** Return a tag to use when the [Log] call has no explicit tag. */
public fun getTag(): String
}
7 changes: 7 additions & 0 deletions logging/src/commonTest/kotlin/Call.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.juul.tuulbox.logging

data class Call(
val tag: String,
val message: String,
val throwable: Throwable?
)
46 changes: 46 additions & 0 deletions logging/src/commonTest/kotlin/CallListLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.juul.tuulbox.logging

class CallListLogger : Logger {

private val mutableVerboseCalls = mutableListOf<Call>()
val verboseCalls: List<Call> = mutableVerboseCalls

private val mutableDebugCalls = mutableListOf<Call>()
val debugCalls: List<Call> = mutableDebugCalls

private val mutableInfoCalls = mutableListOf<Call>()
val infoCalls: List<Call> = mutableInfoCalls

private val mutableWarnCalls = mutableListOf<Call>()
val warnCalls: List<Call> = mutableWarnCalls

private val mutableErrorCalls = mutableListOf<Call>()
val errorCalls: List<Call> = mutableErrorCalls

private val mutableAssertCalls = mutableListOf<Call>()
val assertCalls: List<Call> = mutableAssertCalls

override fun verbose(tag: String, message: String, throwable: Throwable?) {
mutableVerboseCalls += Call(tag = tag, message = message, throwable = throwable)
}

override fun debug(tag: String, message: String, throwable: Throwable?) {
mutableDebugCalls += Call(tag = tag, message = message, throwable = throwable)
}

override fun info(tag: String, message: String, throwable: Throwable?) {
mutableInfoCalls += Call(tag = tag, message = message, throwable = throwable)
}

override fun warn(tag: String, message: String, throwable: Throwable?) {
mutableWarnCalls += Call(tag = tag, message = message, throwable = throwable)
}

override fun error(tag: String, message: String, throwable: Throwable?) {
mutableErrorCalls += Call(tag = tag, message = message, throwable = throwable)
}

override fun assert(tag: String, message: String, throwable: Throwable?) {
mutableAssertCalls += Call(tag = tag, message = message, throwable = throwable)
}
}
14 changes: 14 additions & 0 deletions logging/src/commonTest/kotlin/ConstantTagGeneratorTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.juul.tuulbox.logging

import kotlin.test.Test
import kotlin.test.assertEquals

class ConstantTagGeneratorTests {

@Test
fun constantTagGeneratorWorks() {
val expected = "Constant"
val generator = ConstantTagGenerator(expected)
assertEquals(expected, generator.getTag())
}
}
Loading

0 comments on commit ae28ac2

Please sign in to comment.