For my current project, there is a lot of shared functionality which will be used by both standalone features and an android app.
For this, I wish to write the dependencies in java, compile then into local jars and import them into my android project via gradle (or similar for other applications).
As an example dependency I have created a simple maven project as follows (com.example.shared)…
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>shared</artifactId>
<version>1.0.0</version>
<name>Shared</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
src/main/java/com/example/shared/ExampleSharedClass.java:
package com.example.shared;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ExampleSharedClass {
public static Logger logger = LogManager.getLogger(ExampleSharedClass.class);
public ExampleSharedClass(){
logger.info("Using ExampleSharedClass!");
}
public void printDebuMsg(String msg){
logger.debug(msg);
}
}
My android project is setup as follows…
app/build.gradle:
plugins {
id 'com.android.application'
}
android {
compileSdk 34
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.android"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
resources.excludes.add("META-INF/*")
resources.excludes.add("serviceAccountKey*.json")
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.example:shared:1.0.0'
implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
//runtimeOnly 'com.celeral:log4j2-android:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}
app/src/main/assets/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="console" />
</Root>
</Loggers>
</Configuration>
app/src/main/java/com/example/android/MainActivity.java:
package com.example.android;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MainActivity extends AppCompatActivity {
public static Logger logger = LogManager.getLogger(MainActivity.class);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logger.info("Launched Android App!");
logger.debug("Printing a debug line");
}
}
If I attempt to compile and run the above with only log4j-api: & log4j-core implementations, the logging will fail with the error below:
W/System.err: ERROR StatusLogger Unable to load services for service class org.apache.logging.log4j.spi.Provider
W/System.err: java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or its super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
W/System.err: at org.apache.logging.log4j.util.ServiceLoaderUtil.callServiceLoader(ServiceLoaderUtil.java:110)
W/System.err: at org.apache.logging.log4j.util.ServiceLoaderUtil$ServiceLoaderSpliterator.<init>(ServiceLoaderUtil.java:146)
W/System.err: at org.apache.logging.log4j.util.ServiceLoaderUtil.loadClassloaderServices(ServiceLoaderUtil.java:101)
W/System.err: at org.apache.logging.log4j.util.ServiceLoaderUtil.loadServices(ServiceLoaderUtil.java:83)
W/System.err: at org.apache.logging.log4j.util.ServiceLoaderUtil.loadServices(ServiceLoaderUtil.java:77)
W/System.err: at org.apache.logging.log4j.util.ProviderUtil.<init>(ProviderUtil.java:67)
W/System.err: at org.apache.logging.log4j.util.ProviderUtil.lazyInit(ProviderUtil.java:145)
W/System.err: at org.apache.logging.log4j.util.ProviderUtil.hasProviders(ProviderUtil.java:129)
W/System.err: at org.apache.logging.log4j.LogManager.<clinit>(LogManager.java:90)
W/System.err: at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:598)
W/System.err: at com.example.android.MainActivity.<clinit>(MainActivity.java:14)
W/System.err: at java.lang.Class.newInstance(Native Method)
W/System.err: at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
W/System.err: at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44)
W/System.err: at android.app.Instrumentation.newActivity(Instrumentation.java:1253)
W/System.err: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3353)
W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
W/System.err: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
W/System.err: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
W/System.err: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:106)
W/System.err: at android.os.Looper.loop(Looper.java:223)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:7656)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
If I include a runTimeOnly dependency such as runtimeOnly 'com.celeral:log4j2-android:1.0.0'
then the error will be resolved, but now all lines will be printed regardless of what level of logging is configured.
In fact, if the log4j2.xml file is removed from the assets folder directly then this is not affected suggesting some default configuration that enables all logging levels is in use.
Switching to other android log4j implementations may fix this issue for the 2 lines printed by MainActivity directly, but will still fail for any logging provided by the shared class.
Is there a way to correctly configure logging to work with both android and the shared dependencies?