Skip to content

Commit 0e632f0

Browse files
committed
Fix Android-related issues in Log4j Core (#3071)
This fixes three issues encountered in the [`log4j-samples-android`](https://github.com/apache/logging-log4j-samples/tree/main/log4j-samples-android) test project: 1. Disables the `jvmrunargs` lookup on Android and fixes it on the other platforms. Previously, the lookup always returned `null`. 2. Switches the default context selector to `BasicContextSelector` on Android. `StackLocator` is broken on Android: it cannot use our JDK 8 code (missing `sun.reflect` classes), but also it cannot use our JDK 11+ code (missing multi-release JAR support). This causes `ClassLoaderContextSelector` to use two different logger contexts for the same classloader. 3. Fixes a `ParserConfigurationException` caused by the lack of XInclude capabilities in Android's XML parser. The fix to [LOG4J2-3531](https://issues.apache.org/jira/browse/LOG4J2-3531) didn't cover all the cases. Closes #3056. Part of #2832.
1 parent 91dcf49 commit 0e632f0

File tree

7 files changed

+134
-17
lines changed

7 files changed

+134
-17
lines changed

log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,16 @@ private static void setFeature(
218218
*/
219219
private static void enableXInclude(final DocumentBuilderFactory factory) {
220220
try {
221-
// Alternative: We set if a system property on the command line is set, for example:
222-
// -DLog4j.XInclude=true
223221
factory.setXIncludeAware(true);
224222
// LOG4J2-3531: Xerces only checks if the feature is supported when creating a factory. To reproduce:
225223
// -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XML11NonValidatingConfiguration
226-
factory.newDocumentBuilder();
227-
} catch (final UnsupportedOperationException | ParserConfigurationException e) {
228-
factory.setXIncludeAware(false);
224+
try {
225+
factory.newDocumentBuilder();
226+
} catch (final ParserConfigurationException e) {
227+
factory.setXIncludeAware(false);
228+
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
229+
}
230+
} catch (final UnsupportedOperationException e) {
229231
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
230232
} catch (final AbstractMethodError | NoSuchMethodError err) {
231233
LOGGER.warn(

log4j-core/src/main/java/org/apache/logging/log4j/core/impl/CoreDefaultBundle.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@
4040
import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
4141
import org.apache.logging.log4j.core.lookup.StrLookup;
4242
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
43+
import org.apache.logging.log4j.core.selector.BasicContextSelector;
4344
import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
4445
import org.apache.logging.log4j.core.selector.ContextSelector;
4546
import org.apache.logging.log4j.core.time.Clock;
4647
import org.apache.logging.log4j.core.time.NanoClock;
4748
import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
4849
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
4950
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
51+
import org.apache.logging.log4j.core.util.internal.SystemUtils;
5052
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
5153
import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
5254
import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
@@ -157,7 +159,9 @@ public RecyclerFactory defaultRecyclerFactory(
157159
@SingletonFactory
158160
@ConditionalOnMissingBinding
159161
public ContextSelector defaultContextSelector(final ConfigurableInstanceFactory instanceFactory) {
160-
return new ClassLoaderContextSelector(instanceFactory);
162+
return SystemUtils.isOsAndroid()
163+
? new BasicContextSelector(instanceFactory)
164+
: new ClassLoaderContextSelector(instanceFactory);
161165
}
162166

163167
@SingletonFactory

log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.apache.logging.log4j.core.lookup;
1818

1919
import java.lang.management.ManagementFactory;
20-
import java.util.List;
2120
import java.util.Map;
21+
import org.apache.logging.log4j.Logger;
2222
import org.apache.logging.log4j.core.LogEvent;
23+
import org.apache.logging.log4j.core.util.internal.SystemUtils;
2324
import org.apache.logging.log4j.plugins.Plugin;
2425
import org.apache.logging.log4j.plugins.PluginFactory;
26+
import org.apache.logging.log4j.status.StatusLogger;
2527
import org.apache.logging.log4j.util.Lazy;
2628

2729
/**
@@ -34,23 +36,17 @@
3436
@Plugin("jvmrunargs")
3537
public class JmxRuntimeInputArgumentsLookup extends MapLookup {
3638

39+
private static final Logger LOGGER = StatusLogger.getLogger();
40+
3741
private static final Lazy<JmxRuntimeInputArgumentsLookup> INSTANCE = Lazy.lazy(() -> {
38-
final List<String> argsList = ManagementFactory.getRuntimeMXBean().getInputArguments();
39-
return new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList));
42+
return new JmxRuntimeInputArgumentsLookup(getMapFromJmx());
4043
});
4144

4245
@PluginFactory
4346
public static JmxRuntimeInputArgumentsLookup getInstance() {
4447
return INSTANCE.get();
4548
}
4649

47-
/**
48-
* Constructor when used directly as a plugin.
49-
*/
50-
public JmxRuntimeInputArgumentsLookup() {
51-
super();
52-
}
53-
5450
public JmxRuntimeInputArgumentsLookup(final Map<String, String> map) {
5551
super(map);
5652
}
@@ -63,4 +59,15 @@ public String lookup(final LogEvent ignored, final String key) {
6359
final Map<String, String> map = getMap();
6460
return map == null ? null : map.get(key);
6561
}
62+
63+
private static Map<String, String> getMapFromJmx() {
64+
if (!SystemUtils.isOsAndroid()) {
65+
try {
66+
return MapLookup.toMap(ManagementFactory.getRuntimeMXBean().getInputArguments());
67+
} catch (LinkageError e) {
68+
LOGGER.warn("Failed to get JMX arguments from JVM.", e);
69+
}
70+
}
71+
return Map.of();
72+
}
6673
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.core.util.internal;
18+
19+
import org.apache.logging.log4j.Logger;
20+
import org.apache.logging.log4j.status.StatusLogger;
21+
22+
public final class SystemUtils {
23+
24+
private static final Logger LOGGER = StatusLogger.getLogger();
25+
26+
private static String getJavaVendor() {
27+
try {
28+
return System.getProperty("java.vendor");
29+
} catch (final SecurityException e) {
30+
LOGGER.warn("Unable to determine Java vendor.", e);
31+
}
32+
return "Unknown";
33+
}
34+
35+
public static boolean isOsAndroid() {
36+
return getJavaVendor().contains("Android");
37+
}
38+
39+
private SystemUtils() {}
40+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="3056" link="https://github.com/apache/logging-log4j2/issues/3056"/>
7+
<description format="asciidoc">Fix Android-related issues in Log4j Core.</description>
8+
</entry>

src/site/antora/modules/ROOT/pages/faq.adoc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,55 @@ When you are using Log4j with ProGuard/R8 enabled, you need to add the following
214214
----
215215
-keep,allowoptimization class org.apache.logging.log4j.** { *; }
216216
----
217+
218+
[#android]
219+
== Can I use Log4j with Android?
220+
221+
Of course, you can!
222+
Since version `2.25.0` both the Log4j API and our three Log4j API implementations are tested for compatibility with the Android platform.
223+
224+
If you use
225+
xref:manual/api.adoc[Log4j API]
226+
in an Android project, you have four choices for the Log4j API implementation:
227+
228+
[#android-log4j-core]
229+
Log4j Core::
230+
+
231+
Our
232+
xref:manual/implementation.adoc[reference Log4j API implementation]
233+
works on Android out-of-the-box.
234+
However, due to the limitations of the Android platform, the following features will not work:
235+
+
236+
* The
237+
xref:manual/configuration.adoc#xinclude[XInclude feature]
238+
for XML configuration files will not work if you are using the standard Android XML parser.
239+
You might need to add the
240+
https://xerces.apache.org/[Xerces parser]
241+
to use the feature.
242+
* Due to the lack of Android support for multi-release JARs, some location-based features like the no-arg
243+
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getLogger()[`LogManager.getLogger()`]
244+
method or
245+
xref:manual/systemproperties.adoc#log4j.loggerContext.selector[`ClassLoaderContextSelector`]
246+
(default on JRE) are not available.
247+
You should use `BasicContextSelector` (default on Android) or `BasicAsyncLoggerContextSelector` instead.
248+
249+
[#android-jul]
250+
JUL::
251+
[#android-logback]
252+
Logback::
253+
+
254+
Both our
255+
xref:manual/installation.adoc#impl-jul[Log4j API-to-JUL]
256+
and
257+
xref:manual/installation.adoc#impl-logback[Log4j API-to-SLF4J]
258+
bridges are tested for compatibility with Android.
259+
260+
[#android-native]
261+
Log4j API-to-Android logging API bridge::
262+
+
263+
If you wish to bridge Log4j API to
264+
https://developer.android.com/reference/android/util/Log[Android's native logging API]
265+
directly, you can use the **third-party** `com.celeral:log4j2-android` artifact.
266+
See the
267+
https://github.com/Celeral/log4j2-android[`log4j2-android` project website]
268+
for more information.

src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-logger-context.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
| link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ContextSelector.html[`Class<? extends ContextSelector>`]
2727
2828
| Default value
29-
| link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]
29+
|
30+
link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]
31+
32+
(on Android)
33+
link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/BasicContextSelector.html[`BasicContextSelector`]
3034
|===
3135
3236
Specifies the fully qualified class name of the

0 commit comments

Comments
 (0)