Skip to content

Commit

Permalink
Fix Android-related issues in Log4j Core (#3071)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ppkarwasz authored Oct 10, 2024
1 parent 1e1d9b7 commit f7c26cd
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,16 @@ private static void setFeature(
*/
private static void enableXInclude(final DocumentBuilderFactory factory) {
try {
// Alternative: We set if a system property on the command line is set, for example:
// -DLog4j.XInclude=true
factory.setXIncludeAware(true);
// LOG4J2-3531: Xerces only checks if the feature is supported when creating a factory. To reproduce:
// -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XML11NonValidatingConfiguration
factory.newDocumentBuilder();
} catch (final UnsupportedOperationException | ParserConfigurationException e) {
factory.setXIncludeAware(false);
try {
factory.newDocumentBuilder();
} catch (final ParserConfigurationException e) {
factory.setXIncludeAware(false);
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
}
} catch (final UnsupportedOperationException e) {
LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
} catch (final AbstractMethodError | NoSuchMethodError err) {
LOGGER.warn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.selector.BasicContextSelector;
import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
import org.apache.logging.log4j.core.selector.ContextSelector;
import org.apache.logging.log4j.core.util.Cancellable;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.core.util.internal.SystemUtils;
import org.apache.logging.log4j.spi.LoggerContextFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
Expand Down Expand Up @@ -104,7 +106,12 @@ private static ContextSelector createContextSelector() {
} catch (final Exception e) {
LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e);
}
return new ClassLoaderContextSelector();
// StackLocator is broken on Android:
// 1. Android could use the StackLocator implementation for JDK 11, but does not support multi-release JARs.
// 2. Android does not have the `sun.reflect` classes used in the JDK 8 implementation.
//
// Therefore, we use a single logger context.
return SystemUtils.isOsAndroid() ? new BasicContextSelector() : new ClassLoaderContextSelector();
}

private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
package org.apache.logging.log4j.core.lookup;

import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Collections;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.util.internal.SystemUtils;
import org.apache.logging.log4j.status.StatusLogger;

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

static {
final List<String> argsList = ManagementFactory.getRuntimeMXBean().getInputArguments();
JMX_SINGLETON = new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList));
}
private static final Logger LOGGER = StatusLogger.getLogger();

public static final JmxRuntimeInputArgumentsLookup JMX_SINGLETON;
public static final JmxRuntimeInputArgumentsLookup JMX_SINGLETON = new JmxRuntimeInputArgumentsLookup();

/**
* Constructor when used directly as a plugin.
*/
public JmxRuntimeInputArgumentsLookup() {}
public JmxRuntimeInputArgumentsLookup() {
this(getMapFromJmx());
}

public JmxRuntimeInputArgumentsLookup(final Map<String, String> map) {
super(map);
Expand All @@ -55,4 +57,15 @@ public String lookup(final LogEvent ignored, final String key) {
final Map<String, String> map = getMap();
return map == null ? null : map.get(key);
}

private static Map<String, String> getMapFromJmx() {
if (!SystemUtils.isOsAndroid()) {
try {
return MapLookup.toMap(ManagementFactory.getRuntimeMXBean().getInputArguments());
} catch (LinkageError e) {
LOGGER.warn("Failed to get JMX arguments from JVM.", e);
}
}
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.core.util.internal;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.status.StatusLogger;

public final class SystemUtils {

private static final Logger LOGGER = StatusLogger.getLogger();

private static String getJavaVendor() {
try {
return System.getProperty("java.vendor");
} catch (final SecurityException e) {
LOGGER.warn("Unable to determine Java vendor.", e);
}
return "Unknown";
}

public static boolean isOsAndroid() {
return getJavaVendor().contains("Android");
}

private SystemUtils() {}
}
8 changes: 8 additions & 0 deletions src/changelog/.2.x.x/3056_android_support.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="3056" link="https://github.com/apache/logging-log4j2/issues/3056"/>
<description format="asciidoc">Fix Android-related issues in Log4j Core.</description>
</entry>
52 changes: 52 additions & 0 deletions src/site/antora/modules/ROOT/pages/faq.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,55 @@ https://gradleup.com/shadow/[Gradle Shadow Plugin]:::
You need to use the
https://github.com/GradleUp/shadow/blob/main/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy[`Log4j2PluginsCacheFileTransformer`].
====
[#android]
== Can I use Log4j with Android?
Of course, you can!
Since version `2.25.0` both the Log4j API and our three Log4j API implementations are tested for compatibility with the Android platform.
If you use
xref:manual/api.adoc[Log4j API]
in an Android project, you have four choices for the Log4j API implementation:
[#android-log4j-core]
Log4j Core::
+
Our
xref:manual/implementation.adoc[reference Log4j API implementation]
works on Android out-of-the-box.
However, due to the limitations of the Android platform, the following features will not work:
+
* The
xref:manual/configuration.adoc#xinclude[XInclude feature]
for XML configuration files will not work if you are using the standard Android XML parser.
You might need to add the
https://xerces.apache.org/[Xerces parser]
to use the feature.
* Due to the lack of Android support for multi-release JARs, some location-based features like the no-arg
link:javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getLogger()[`LogManager.getLogger()`]
method or
xref:manual/systemproperties.adoc#log4j2.contextSelector[`ClassLoaderContextSelector`]
(default on JRE) are not available.
You should use `BasicContextSelector` (default on Android) or `BasicAsyncLoggerContextSelector` instead.
[#android-jul]
JUL::
[#android-logback]
Logback::
+
Both our
xref:manual/installation.adoc#impl-jul[Log4j API-to-JUL]
and
xref:manual/installation.adoc#impl-logback[Log4j API-to-SLF4J]
bridges are tested for compatibility with Android.
[#android-native]
Log4j API-to-Android logging API bridge::
+
If you wish to bridge Log4j API to
https://developer.android.com/reference/android/util/Log[Android's native logging API]
directly, you can use the **third-party** `com.celeral:log4j2-android` artifact.
See the
https://github.com/Celeral/log4j2-android[`log4j2-android` project website]
for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
| link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ContextSelector.html[`Class<? extends ContextSelector>`]
| Default value
| link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]
|
link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]
(on Android)
link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/BasicContextSelector.html[`BasicContextSelector`]
|===
Specifies the fully qualified class name of the
Expand Down

0 comments on commit f7c26cd

Please sign in to comment.