https://github.com/mflisar/lumberjack
Lumberjack - a customisable and extensible file/console logger (with optional Timber support)
https://github.com/mflisar/lumberjack
android logging lumberjack timber
Last synced: 15 days ago
JSON representation
Lumberjack - a customisable and extensible file/console logger (with optional Timber support)
- Host: GitHub
- URL: https://github.com/mflisar/lumberjack
- Owner: MFlisar
- License: apache-2.0
- Created: 2016-10-20T12:25:42.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2026-06-06T17:42:01.000Z (17 days ago)
- Last Synced: 2026-06-06T19:16:21.680Z (17 days ago)
- Topics: android, logging, lumberjack, timber
- Language: Kotlin
- Homepage: https://mflisar.github.io/Lumberjack/
- Size: 5.97 MB
- Stars: 46
- Watchers: 1
- Forks: 7
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://central.sonatype.com/artifact/io.github.mflisar.lumberjack/core)   
# Lumberjack
     
This library provides following main features:
* simply lazy evalution based logger based on a `L` class for the shortest possible log codes
* easily extendible with custom loggers
* offers console and file logger implementations
* supports log filtering
* providers a viewer for log files
# Table of Contents
- [Screenshots](#camera-screenshots)
- [Supported Platforms](#computer-supported-platforms)
- [Versions](#arrow_right-versions)
- [Setup](#wrench-setup)
- [Usage](#rocket-usage)
- [Modules](#file_folder-modules)
- [Demo](#sparkles-demo)
- [More](#information_source-more)
- [API](#books-api)
- [Other Libraries](#bulb-other-libraries)
# :camera: Screenshots
### composeviewer
| | | |
|---|---|---|
|  |  |
### core
| | | |
|---|---|---|
|  |  |
# :computer: Supported Platforms
| Module | android | iOS | windows | macOS | wasm | Notes |
|---|---|---|---|---|---|---|
| core | ✅ | ✅ | ✅ | ✅ | ✅ | the core module of lumberjack |
| implementation | ✅ | ✅ | ✅ | ✅ | ✅ | the implementation of lumberjack |
| logger-console | ✅ | ✅ | ✅ | ✅ | ✅ | a console logger for lumberjack |
| logger-file | ✅ | ✅ | ✅ | ✅ | ❌ | a file logger for lumberjack |
| extension-composeviewer | ✅ | ✅ | ✅ | ✅ | ✅ | a compose viewer for lumberjack |
| extension-feedback | ✅ | ✅ | ❌ | ❌ | ❌ | a feedback module for lumberjack |
# :arrow_right: Versions
| Dependency | Version |
|---|---|
| Kotlin | `2.4.0` |
| Jetbrains Compose | `1.11.1` |
| Jetbrains Compose Material3 | `1.9.0` |
> :warning: Following experimental annotations are used:
> - **OptIn**
> - `androidx.compose.material3.ExperimentalMaterial3Api` (3x)
> - `kotlinx.cinterop.ExperimentalForeignApi` (2x)
>
> I try to use as less experimental features as possible, but in this case the ones above are needed!
# :wrench: Setup
Using Version Catalogs
Define the dependencies inside your **libs.versions.toml** file.
```toml
[versions]
lumberjack = ""
[libraries]
lumberjack-core = { module = "io.github.mflisar.lumberjack:core", version.ref = "lumberjack" }
lumberjack-implementation = { module = "io.github.mflisar.lumberjack:implementation", version.ref = "lumberjack" }
lumberjack-logger-console = { module = "io.github.mflisar.lumberjack:logger-console", version.ref = "lumberjack" }
lumberjack-logger-file = { module = "io.github.mflisar.lumberjack:logger-file", version.ref = "lumberjack" }
lumberjack-extension-composeviewer = { module = "io.github.mflisar.lumberjack:extension-composeviewer", version.ref = "lumberjack" }
lumberjack-extension-feedback = { module = "io.github.mflisar.lumberjack:extension-feedback", version.ref = "lumberjack" }
```
And then use the definitions in your projects **build.gradle.kts** file like following:
```java
implementation(libs.lumberjack.core)
implementation(libs.lumberjack.implementation)
implementation(libs.lumberjack.logger.console)
implementation(libs.lumberjack.logger.file)
implementation(libs.lumberjack.extension.composeviewer)
implementation(libs.lumberjack.extension.feedback)
```
Direct Dependency Notation
Simply add the dependencies inside your **build.gradle.kts** file.
```kotlin
val lumberjack = ""
implementation("io.github.mflisar.lumberjack:core:${lumberjack}")
implementation("io.github.mflisar.lumberjack:implementation:${lumberjack}")
implementation("io.github.mflisar.lumberjack:logger-console:${lumberjack}")
implementation("io.github.mflisar.lumberjack:logger-file:${lumberjack}")
implementation("io.github.mflisar.lumberjack:extension-composeviewer:${lumberjack}")
implementation("io.github.mflisar.lumberjack:extension-feedback:${lumberjack}")
```
# :rocket: Usage
#### 1. Define a `SettingsModel`
```kotlin
// Depending on the platform:
// - common: DataStoreStorage.create(name = "preferences")
// - jvm: DataStoreStorage.create(folder = File(System.getProperty("user.dir")), name = "preferences")
// - android/iOS: DataStoreStorage.create(name = "preferences")
// - ...
object Preferences : SettingsModel(DataStoreStorage.create(name = "preferences")) {
// main data types
val someString by stringPref("value")
val someBool by boolPref(false)
val someInt by intPref(123)
val someLong by intPref(123L)
val someFloat by intPref(123f)
val someDouble by intPref(123.0)
// enum
val someEnum by enumPref(Enum.Value1)
// custom
val someCustomClass1 by anyStringPref(TestClass.CONVERTER, TestClass()) // converts TestClass to a string and saves this string
val someCustomClass2 by anyIntPref(TestClass.CONVERTER, TestClass()) // converts TestClass to an int and saves this int
val someCustomClass3 by anyLongPref(TestClass.CONVERTER, TestClass()) // converts TestClass to a long and saves this long
// sets
val someStringSet by stringSetPref(setOf("a"))
val someIntSet by intSetPref(setOf(1))
val someLongSet by longSetPref(setOf(1L))
val someFloatSet by floatSetPref(setOf(1f))
val someDoubleSet by doubleSetPref(setOf(1.0))
// NULLABLE vs NON NULLABLE
val nonNullableString by stringPref()
val nullableString by nullableStringPref()
val nonNullableInt by intPref()
val nullableInt by nullableIntPref()
val nonNullableFloat by floatPref()
val nullableFloat by nullableFloatPref()
val nonNullableDouble by doublePref()
val nullableDouble by nullableDoublePref()
val nonNullableLong by longPref()
val nullableLong by nullableLongPref()
val nonNullableBool by boolPref()
val nullableBool by nullableBoolPref()
// custom
val someCustomClass4 by nullableAnyStringPref(TestClass.CONVERTER, TestClass())
val someCustomClass5 by nullableAnyIntPref(TestClass.CONVERTER, TestClass())
val someCustomClass6 by nullableAnyLongPref(TestClass.CONVERTER, TestClass())
}
```
#### 2a) Use the `SettingsModel` in plain kotlin (`flows` + `suspending functions`)
```kotlin
// 1) get a flow
val flow = Preferences.someString.flow
// 2) read/update values by suspend functions
scope.launch(Dispatchers.IO) {
val value = Preferences.someInt.read()
Preferences.someInt.update(value + 1)
}
```
#### 2b) Use the `SettingsModel` in views (e.g. with `Lifecycle Scope`)
```kotlin
// 1) simply observe a setting
Preferences.someString.observe(lifecycleScope) {
L.d { "someString = $it"}
}
// 2) direct read (not recommended if not necessary but may be useful in many cases)
// => simply returns read() in a blocking way)
val name = Preferences.someString.value
// 3) observe a setting once
Preferences.someString.observeOnce(lifecycleScope) {
L.d { "someString = $it"}
}
// 4) observe ALL settings
Preferences.changes.onEach {
L.d { "[ALL SETTINGS OBSERVER] Setting '${it.setting.key}' changed its value to ${it.value}" }
}.launchIn(lifecycleScope)
// 5) observe SOME settings
Preferences.changes
.filter {
it.setting == Preferences.someString ||
it.setting == Preferences.someBool
}.onEach {
L.d { "[SOME SETTINGS OBSERVER] Setting '${it.setting.key}' changed its value to ${it.value}" }
}.launchIn(lifecycleScope)
// 6) read multiple settings in a suspending way
lifecycleScope.launch(Dispatchers.IO) {
val someString = Preferences.someString.read()
val someBool = Preferences.someBool.read()
}
```
#### 2c) Use the `SettingsModel` in `compose`
```kotlin
val name = Preferences.someString.collectAsState()
val name = Preferences.someString.collectAsStateWithLifecycle()
// simply use the state inside your composables, the state will change whenever the setting behind it will change
val name = Preferences.someString.asMutableState()
// simple use and even update the state now
```
# :file_folder: Modules
- [Compose Viewer](documentation/Modules/Compose%20Viewer.md)
- [Feedback](documentation/Modules/Feedback.md)
# :sparkles: Demo
A full [demo](/demo) is included inside the demo module, it shows nearly every usage with working examples.
# :information_source: More
- Advanced
- [Custom Logger](documentation/Advanced/Custom%20Logger.md)
- Migration
- [Migration to version 10](documentation/Migration/v10.md)
- [Migration to version 7](documentation/Migration/v7.md)
# :books: API
Check out the [API documentation](https://MFlisar.github.io/Lumberjack/).
# :bulb: Other Libraries
You can find more libraries (all multiplatform) of mine that all do work together nicely [here](https://mflisar.github.io/Libraries/).