Skip to content

Commit f7c26cd

Browse files
authored
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 1e1d9b7 commit f7c26cd

File tree

7 files changed

+140
-14
lines changed

7 files changed

+140
-14
lines changed

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,16 @@ private static void setFeature(
216216
*/
217217
private static void enableXInclude(final DocumentBuilderFactory factory) {
218218
try {
219-
// Alternative: We set if a system property on the command line is set, for example:
220-
// -DLog4j.XInclude=true
221219
factory.setXIncludeAware(true);
222220
// LOG4J2-3531: Xerces only checks if the feature is supported when creating a factory. To reproduce:
223221
// -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XML11NonValidatingConfiguration
224-
factory.newDocumentBuilder();
225-
} catch (final UnsupportedOperationException | ParserConfigurationException e) {
226-
factory.setXIncludeAware(false);
222+
try {
223+
factory.newDocumentBuilder();
224+
} catch (final ParserConfigurationException e) {
225+
factory.setXIncludeAware(false);
226+
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
227+
}
228+
} catch (final UnsupportedOperationException e) {
227229
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
228230
} catch (final AbstractMethodError | NoSuchMethodError err) {
229231
LOGGER.warn(

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
import org.apache.logging.log4j.core.config.ConfigurationSource;
3030
import org.apache.logging.log4j.core.config.DefaultConfiguration;
3131
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
32+
import org.apache.logging.log4j.core.selector.BasicContextSelector;
3233
import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
3334
import org.apache.logging.log4j.core.selector.ContextSelector;
3435
import org.apache.logging.log4j.core.util.Cancellable;
3536
import org.apache.logging.log4j.core.util.Constants;
3637
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
3738
import org.apache.logging.log4j.core.util.Loader;
3839
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
40+
import org.apache.logging.log4j.core.util.internal.SystemUtils;
3941
import org.apache.logging.log4j.spi.LoggerContextFactory;
4042
import org.apache.logging.log4j.status.StatusLogger;
4143
import org.apache.logging.log4j.util.PropertiesUtil;
@@ -104,7 +106,12 @@ private static ContextSelector createContextSelector() {
104106
} catch (final Exception e) {
105107
LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e);
106108
}
107-
return new ClassLoaderContextSelector();
109+
// StackLocator is broken on Android:
110+
// 1. Android could use the StackLocator implementation for JDK 11, but does not support multi-release JARs.
111+
// 2. Android does not have the `sun.reflect` classes used in the JDK 8 implementation.
112+
//
113+
// Therefore, we use a single logger context.
114+
return SystemUtils.isOsAndroid() ? new BasicContextSelector() : new ClassLoaderContextSelector();
108115
}
109116

110117
private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {

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

+20-7
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package org.apache.logging.log4j.core.lookup;
1818

1919
import java.lang.management.ManagementFactory;
20-
import java.util.List;
20+
import java.util.Collections;
2121
import java.util.Map;
22+
import org.apache.logging.log4j.Logger;
2223
import org.apache.logging.log4j.core.LogEvent;
2324
import org.apache.logging.log4j.core.config.plugins.Plugin;
25+
import org.apache.logging.log4j.core.util.internal.SystemUtils;
26+
import org.apache.logging.log4j.status.StatusLogger;
2427

2528
/**
2629
* Maps JVM input arguments (but not main arguments) using JMX to acquire JVM arguments.
@@ -31,17 +34,16 @@
3134
@Plugin(name = "jvmrunargs", category = StrLookup.CATEGORY)
3235
public class JmxRuntimeInputArgumentsLookup extends MapLookup {
3336

34-
static {
35-
final List<String> argsList = ManagementFactory.getRuntimeMXBean().getInputArguments();
36-
JMX_SINGLETON = new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList));
37-
}
37+
private static final Logger LOGGER = StatusLogger.getLogger();
3838

39-
public static final JmxRuntimeInputArgumentsLookup JMX_SINGLETON;
39+
public static final JmxRuntimeInputArgumentsLookup JMX_SINGLETON = new JmxRuntimeInputArgumentsLookup();
4040

4141
/**
4242
* Constructor when used directly as a plugin.
4343
*/
44-
public JmxRuntimeInputArgumentsLookup() {}
44+
public JmxRuntimeInputArgumentsLookup() {
45+
this(getMapFromJmx());
46+
}
4547

4648
public JmxRuntimeInputArgumentsLookup(final Map<String, String> map) {
4749
super(map);
@@ -55,4 +57,15 @@ public String lookup(final LogEvent ignored, final String key) {
5557
final Map<String, String> map = getMap();
5658
return map == null ? null : map.get(key);
5759
}
60+
61+
private static Map<String, String> getMapFromJmx() {
62+
if (!SystemUtils.isOsAndroid()) {
63+
try {
64+
return MapLookup.toMap(ManagementFactory.getRuntimeMXBean().getInputArguments());
65+
} catch (LinkageError e) {
66+
LOGGER.warn("Failed to get JMX arguments from JVM.", e);
67+
}
68+
}
69+
return Collections.emptyMap();
70+
}
5871
}
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+
}
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

+52
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,55 @@ https://gradleup.com/shadow/[Gradle Shadow Plugin]:::
327327
You need to use the
328328
https://github.com/GradleUp/shadow/blob/main/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy[`Log4j2PluginsCacheFileTransformer`].
329329
====
330+
331+
[#android]
332+
== Can I use Log4j with Android?
333+
334+
Of course, you can!
335+
Since version `2.25.0` both the Log4j API and our three Log4j API implementations are tested for compatibility with the Android platform.
336+
337+
If you use
338+
xref:manual/api.adoc[Log4j API]
339+
in an Android project, you have four choices for the Log4j API implementation:
340+
341+
[#android-log4j-core]
342+
Log4j Core::
343+
+
344+
Our
345+
xref:manual/implementation.adoc[reference Log4j API implementation]
346+
works on Android out-of-the-box.
347+
However, due to the limitations of the Android platform, the following features will not work:
348+
+
349+
* The
350+
xref:manual/configuration.adoc#xinclude[XInclude feature]
351+
for XML configuration files will not work if you are using the standard Android XML parser.
352+
You might need to add the
353+
https://xerces.apache.org/[Xerces parser]
354+
to use the feature.
355+
* Due to the lack of Android support for multi-release JARs, some location-based features like the no-arg
356+
link:javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getLogger()[`LogManager.getLogger()`]
357+
method or
358+
xref:manual/systemproperties.adoc#log4j2.contextSelector[`ClassLoaderContextSelector`]
359+
(default on JRE) are not available.
360+
You should use `BasicContextSelector` (default on Android) or `BasicAsyncLoggerContextSelector` instead.
361+
362+
[#android-jul]
363+
JUL::
364+
[#android-logback]
365+
Logback::
366+
+
367+
Both our
368+
xref:manual/installation.adoc#impl-jul[Log4j API-to-JUL]
369+
and
370+
xref:manual/installation.adoc#impl-logback[Log4j API-to-SLF4J]
371+
bridges are tested for compatibility with Android.
372+
373+
[#android-native]
374+
Log4j API-to-Android logging API bridge::
375+
+
376+
If you wish to bridge Log4j API to
377+
https://developer.android.com/reference/android/util/Log[Android's native logging API]
378+
directly, you can use the **third-party** `com.celeral:log4j2-android` artifact.
379+
See the
380+
https://github.com/Celeral/log4j2-android[`log4j2-android` project website]
381+
for more information.

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

+5-1
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)