Lumberjack#
This is a full logging library with a build in way to log to console, file or any custom place as well as optional extensions to send a log file via mail or show it on the device.
Information
The complete library is modularized so that you can just add those parts of it that you are interested in.
Timber Support
This library fully supports Jack Whartons Timber logging library (v4!). And was even based on it until Lumberjack v6. Beginning with v6 I wrote new modules that work without timber which leads to a smaller and more versitile non timber version. I would advice you to use the non timber versions but if you want to you can simply use the timber modules I provide as well - whatever you prefer.
Why did I do this?
I decided to not use Timber
myself anymore because of following reasons:
Timber
does explicitly rely on non lazy evaluating logging - it was a decision made by Jack Wharton and was the main reason to writeLumberjack
at the beginningTimber
is restrictive regarding class extensions - in v5 I would need access to a field to continue supporting timber inLumberjack
Timber
is considered as working and feature requests and/or pull requests are not accepted if not really necessary - like e.g. my minimal one here.- additionally I always needed to extend the
BaseTree
fromTimber
because of the limiting restrictions of the defaultBaseTree
as well as it was to restrictive to make adjustment in it ( I always had a nearly 1:1 copy of it inside my library here). This was needed to allow to adjust the stack trace depth so thatLumberjack
will log the correct calling place as a wrapper aroundTimber
.
This lead to my final decision
Lumberjack
does not need Timber
and I provide a way to plug in Timber
into Lumberjack
now - this way using Timber
and Lumberjack
in combination is possible but not necessary anymore.
Example Outputs#
Console | |
---|---|
File |
---|
Example log file |
Compose Viewer | View Viewer | ||
---|---|---|---|
Features#
- logs will be created with class name, function name abd line number of the calling place automatically
- logs are evaluated lazily, this means, if the content of a log is not needed, it won't be evaluated
- loggers can be enabled/disabled and do support filtering logs
- supports arbitrary loggers by implementing a single function based interface
- can be used with a very small custom logging implementation or timber (whatever you prefer)
- has extensions for
- sending a log file via mail (no internet permissions - this is done by appending the log file to an
Intent
and let the user choose an email client) - a log file viewer (view or compose based)
- a notification extension which allows you to show a notification which can show a non crashing but unexpected error and allows the user to click it and send a log file if desired
- sending a log file via mail (no internet permissions - this is done by appending the log file to an
All features are splitted into separate modules, just include the modules you want to use!
Dependencies#
Compose
Following dependency only applies to the extension-composeviewer module.
Dependency | Version | Infos |
---|---|---|
Compose BOM | 2024.04.00 | Mapping |
Material3 | 1.2.1 |
Library
Module | Dependency | Version | ||
---|---|---|---|---|
core | - | |||
Lumberjack Loggers | | | ||
implementation-lumberjack | - | |||
logger-console | - | |||
logger-file | - | |||
Timber Loggers | | | ||
implementation-timber | Timber | 4.7.1 | ||
logger-timber-console | Timber | 4.7.1 | ||
logger-timber-file | Timber slf4j logback-android | 4.7.1 2.0.7 3.0.0 | ||
Common Extensions | | | ||
extension-feedback | FeedbackManager | 2.0.5 | ||
extension-notification | FeedbackManager | 2.0.5 | ||
extension-viewer | FastScroller FeedbackManager | 1.0.0 2.0.5 | ||
extension-composeviewer | FeedbackManager | 2.0.5 |
Setup Gradle#
This library is distributed via JitPack.io.
2/2: Add dependencies to your module's build.gradle
// use the latest version of the library
val lumberjack = "<LATEST-VERSION>"
// include necessary modules
// core module
implementation("com.github.MFlisar.Lumberjack:core:$lumberjack")
// lumberjack module
implementation("com.github.MFlisar.Lumberjack:implementation-lumberjack:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:logger-console:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:logger-file:$lumberjack")
// timber module
implementation("com.github.MFlisar.Lumberjack:implementation-timber:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:logger-timber-console:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:logger-timber-file:$lumberjack")
// extension module
implementation("com.github.MFlisar.Lumberjack:extension-feedback:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:extension-notification:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:extension-viewer:$lumberjack")
implementation("com.github.MFlisar.Lumberjack:extension-composeviewer:$lumberjack")
Setup Library#
Usage#
Logging
// wherever you want use one of L.* functions for logging
// all the functions are implemented as inline functions with lambdas - this means,
// everything inside the lambda is only executed if the log is really ussed
L.d { "a debug log" }
L.e { "a error log" }
L.e(e)
L.e(e) { "an exception log with an additonal message" }
L.v { "TEST-LOG - Verbose log..." }
L.d { "TEST-LOG - Debug log..." }
L.i { "TEST-LOG - Info log..." }
L.w { "TEST-LOG - Warn log..." }
L.e { "TEST-LOG - Error log..." }
L.wtf { "TEST-LOG - WTF log..." }
// optional tags work like following
L.tag("LEVEL").d { "Tagged log message..." }
// you can log something optionally like following
L.logIf { false }?.d { "This will never be logged because logIf evaluates to false..." }
// manual log levels
L.log(Level.DEBUG) { "Debug level log via L.log instead of L.d" }
Filtering Logs
// typealias LumberjackFilter = (level: Level, tag: String?, time: Long, fileName: String, className: String, methodName: String, line: Int, msg: String?, throwable: Throwable?) -> Boolean
val filter = object : LumberjackFilter {
override fun invoke(
level: Level,
tag: String?,
time: Long,
fileName: String,
className: String,
methodName: String,
line: Int,
msg: String?,
throwable: Throwable?
): Boolean {
// decide if you want to log this message...
return true
}
}
// the filter can then be attached to any logger implementation
val consoleLogger = ConsoleLogger(filter = filter)
val fileLogger = FileLogger(filter = filter)
Lumberjack Version
The lumberjack implementation allows you more granular filter options as well as a custom filter for each logger implementation!
TimberLogger.filter = object: IFilter {
override fun isTagEnabled(baseTree: BaseTree, tag: String): Boolean {
// decide if you want to log this tag on this tree...
return true
}
override fun isPackageNameEnabled(packageName: String): Boolean {
// decide if you want to log if the log comes from a class within the provided package name
return true
}
}
Other settings
// if desired you can enable/disable all logs completely
// e.g. in a release build like following
// => you probably would want to do this inside the application after the init of Lumberjack
L.enable(BuildConfig.DEBUG)
// Alternatively every logger does support an enabled flag as well
val consoleLogger = ConsoleLogger(enabled = BuildConfig.DEBUG)
val fileLogger = FileLogger(enabled = !BuildConfig.DEBUG, ...)
Demo#
A full demo is included inside the demo module, it shows nearly every usage with working examples.
Modules and Extensions#
Depending on your preferences you must decide yourself if you want to use the timber modules or the non timber modules. My suggestion is to prefer the non timber modules as those will save some space and will allow you to even log in a more flexible way. Despite that, all extensions work with any implementation (timber or non timber one)
Extension Feedback
This small extension simply allows you to send a log file via mail (no internet connection required). This will be done by sharing the file as email Intent
.
Extension Notification
This small extension provides you with with a few functions to create notifications (for app testers or for the dev for example) that can be clicked and then will allow the user to send the log file to you via the extension-feedback
. Or to open the log file by clicking the notification.
// shows a notifcation - on notification click the suer can do following:
// * nothing
// * send a mail with optional attachments like e.g. log files, database, whatever
// * execute a custom action
fun L.showNotification(
context: Context,
notificationIcon: Int,
notificationChannelId: String,
notificationId: Int,
notificationTitle: String = "Rare exception found",
notificationText: String = "Please report this error by clicking this notification, thanks",
clickHandler: NotificationClickHandler
)
// Click Handlers
// here's a short overview of the available click handlers
sealed class NotificationClickHandler {
class SendFeedback(
context: Context,
val receiver: String,
val subject: String = "Exception found in ${context.packageName}",
val titleForChooser: String = "Send report with",
val attachments: List<File> = emptyList()
) : NotificationClickHandler()
class ClickIntent(
val intent: Intent,
val apply: ((builder: NotificationCompat.Builder) -> Unit)? = null
): NotificationClickHandler()
data object None: NotificationClickHandler()
}
Extension ComposeViewer
If you use compose in your app you should use this viewer - it allows you to show log files directly inside your app.
val showLogViewer = rememberSaveable {
mutableStateOf(false)
}
LumberjackDialog(
visible = showLogViewer,
title = "Logs",
setup = <a file logging setup>,
mail = "some.mail@gmail.com"
)
Compose Viewer | |
---|---|
Extension Viewer
// show the log viewer activity (mail address is optional,
// if it's null, the send mail entry will be removed from the viewers menu)
L.showLog(
context,
fileLoggingSetup,
"some.mail@gmail.com"
)
Viewer | |
---|---|
Advanced Usage#
Either use the timber version and plug in your custom loggers into timber (check out timber for that please) or simply plug in a custom logger into lumberjack directly if you do not use the timber solution like following - all you need to do is implementing a single function and then add your logger to Lumberjack
(following example is the current ConsoleLogger
implementation).
Custom Logger Example
class ConsoleLogger(
override var enabled: Boolean = true,
override val filter: LumberjackFilter = DefaultLumberjackFilter
) : ILumberjackLogger {
override fun log(
level: Level,
tag: String?,
time: Long,
fileName: String,
className: String,
methodName: String,
line: Int,
msg: String?,
throwable: Throwable?
) {
val link = "(${fileName}:${line})"
val log = listOfNotNull(
msg,
link.takeIf { throwable == null },
throwable?.stackTraceToString()?.let { "\n$it" }
).joinToString(" ")
Log.println(level.priority, tag, log)
}
}
That's all. You can do the logging asynchronous as well if you want - just do whatever you want inside your logger implementation.
Notes#
File Loggers#
There's something to say about file loggers. The timber
version uses slf4j
+ logback-android
which adds quite some overhead to your app. But those libraries are well tested and solid.
Beginning with v6 I decided to also provide non timber versions of my library and the file logger for this one does not have any dependencies - it simply logs in a background thread with the help of coroutines. This makes this alternative very tiny.