Yet another one. But this one works.
- Seamless integration of Log4J over SLF4J into your Android application
- File-based configuration of logging for fine control - just like in proper Java
- Separate configurations for Debug, Release, JUnit tests, and Android tests
- Extension for JUnit 5
- Rule for JUnit 4
The example section shows the usage of SLF4J facade on the lib side and the log control using Log4J2 on the app side.
dependencies {
implementation 'io.github.neboskreb:android-log4j2:2.24'
androidTestImplementation 'io.github.neboskreb:android-log4j2-junit5:2.24'
}
See full source for app and lib in the examples folder.
The example is found in examples folder. It shows a classic setup of a library The Lib consumed by an application The App. The lib does some logging - which is handy for its developers but not so much for the devs of the App who are more interested in their own logs. And for the release, of course, the noise in logs must be reduced to the minimum. Library android-log4j2
helps to achieve both points easily.
The library should not use any concrete logging engine but rather a logging facade, in this case SLF4J. The choice of the concrete logging implementation is on the consumer of the library.
However, if the developers want to use Log4J2 for testing/debugging they can do it by putting a configuration file for the tests. See chapter Android test below for details.
The app has to say which concrete logging engine is used, and what loglevels are.
Library android-log4j2
offers seamless and easy integration with Log4J2 engine. Just initialize the log context with a one-liner in your app, the rest is magic:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
AndroidLog4jHelper.initialize(this.applicationContext)
}
}
If you omit the initialization, the log will be silent - which might be a very good idea for a Release build.
Normally you don't want your tests to log anything. A properly written test provides all information via assert
s so a developer doesn't need to consult the logs to find why a test failed. As the default loglevel in tests is OFF, you don't need to add anything.
However, if you do need to change the loglevel, you can provide your configuration in androidTest/assets/log4j2.properties
and configure your tests as follows:
NOTE: For instructions how to enable JUnit 5 in your Android project in the first place, please refer to android-junit5 plugin.
dependencies {
androidTestImplementation 'io.github.neboskreb:android-log4j2-junit5:2.24'
}
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(AndroidLog4jExtension::class)
class MyJUnit5AndroidTest {
@Test
fun myTest() {
...
}
}
dependencies {
androidTestImplementation 'io.github.neboskreb:android-log4j2-junit4:2.24'
}
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyJUnit4AndroidTest {
@get:Rule
val rule = AndroidLog4jRule()
@Test
fun myTest() {
...
}
}
No changes to the code required. Put your configuration in test/resources/log4j2.properties
and it just works.
dependencies {
testImplementation 'io.github.neboskreb:android-log4j2:2.24'
}
The Log4J2 configuration file is called log4j2.properties
and is found in the following locations:
- for unit tests, nothing changed. It is where we always had it - in
src/test/resources
- for Android tests, it is in
src/androidTest/assets
- for application, it depends on the build variant:
- for Debug builds: in
src/debug/assets
- for Release builds: in
src/release/assets
- for Debug builds: in
If you do not separate your Debug and Release source sets, you can keep it in src/main/assets
; just don't forget to adjust the log levels before the release.
In Android tests, the configuration from src/androidTest/assets
overrides the config from the app.
If no config file provided, the log will be silent.
Example of a typical log4j2.properties
during the development:
# Root Logger
rootLogger = ALL, LOGCAT
# Direct log messages to LOGCAT
appender.logcat.type = Logcat
appender.logcat.name = LOGCAT
appender.logcat.layout.type = PatternLayout
appender.logcat.layout.pattern = %m%n
appender.logcat.stack-trace-rendering = logcat
# Reduce the noise from the lib:
logger.lib.name = com.github.neboskreb.lib
logger.lib.level = warn
logger.app.name = com.github.neboskreb.app
logger.app.level = all
Select which engine to use for rendering the stack trace when logging the exceptions. Some Android tools might get confused with Log4J's stack traces.
Options: logcat
(default), log4j2
Tags in logcat are supported via Markers
:
import org.slf4j.MarkerFactory
import org.slf4j.LoggerFactory
class MyClass {
private val tag = MarkerFactory.getMarker("MY_TAG")
private val log = LoggerFactory.getLogger(MyClass::class.java)
fun doSomething() {
log.debug(tag, "something")
}
}
Markers are optional. If no marker provided, the tag defaults to the logger name (i.e., "my.package.MyClass" in this case).
The following trick gives you more filtering abilities:
class MyClass {
private val log = LoggerFactory.getLogger("MY_TAG")
private val attention = MarkerFactory.getMarker("MY_TAG:ATT")
private val error = MarkerFactory.getMarker("MY_TAG:ERR")
fun doSomething() {
log.debug("something boring")
log.debug(attention, "something abnormal")
log.debug(error, "something bad")
}
}
In your application, when you include library Android Log4J it will bring the latest version of Log4J as a transitive dependency. So theoretically, including only io.github.neboskreb:android-log4j2:xxx
is enough. However, for better determinism it's always better to pin down the exact version of Log4J:
dependencies {
implementation 'io.github.neboskreb:android-log4j2:2.24'
// Though android-log4j2 brings Log4J2 in as a transitive dependency,
// it is always a good idea to pin the exact version:
implementation 'org.apache.logging.log4j:log4j-core:2.24.3'
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3'
}
Note: in your library, you should NOT include any specific version of Log4J because that might create dependency conflict in the consumer of your library.
- XML configuration is broken due to bug in Android XML parser. Fix is in progress; for now use
.properties
configuration.
This work is based on the example app by Loune.
Pull requests are welcome! If you plan to open one, please first create an issue where you describe the problem/gap your contribution closes, and tag the keeper(s) of this repo so they could get back to you with help.