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 committed Oct 16, 2024
1 parent 91dcf49 commit 0e632f0
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,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 @@ -40,13 +40,15 @@
import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
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.time.Clock;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.core.util.internal.SystemUtils;
import org.apache.logging.log4j.kit.env.PropertyEnvironment;
import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
Expand Down Expand Up @@ -157,7 +159,9 @@ public RecyclerFactory defaultRecyclerFactory(
@SingletonFactory
@ConditionalOnMissingBinding
public ContextSelector defaultContextSelector(final ConfigurableInstanceFactory instanceFactory) {
return new ClassLoaderContextSelector(instanceFactory);
return SystemUtils.isOsAndroid()
? new BasicContextSelector(instanceFactory)
: new ClassLoaderContextSelector(instanceFactory);
}

@SingletonFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package org.apache.logging.log4j.core.lookup;

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

/**
Expand All @@ -34,23 +36,17 @@
@Plugin("jvmrunargs")
public class JmxRuntimeInputArgumentsLookup extends MapLookup {

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

private static final Lazy<JmxRuntimeInputArgumentsLookup> INSTANCE = Lazy.lazy(() -> {
final List<String> argsList = ManagementFactory.getRuntimeMXBean().getInputArguments();
return new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList));
return new JmxRuntimeInputArgumentsLookup(getMapFromJmx());
});

@PluginFactory
public static JmxRuntimeInputArgumentsLookup getInstance() {
return INSTANCE.get();
}

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

public JmxRuntimeInputArgumentsLookup(final Map<String, String> map) {
super(map);
}
Expand All @@ -63,4 +59,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 Map.of();
}
}
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/.3.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 @@ -214,3 +214,55 @@ When you are using Log4j with ProGuard/R8 enabled, you need to add the following
----
-keep,allowoptimization class org.apache.logging.log4j.** { *; }
----
[#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
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getLogger()[`LogManager.getLogger()`]
method or
xref:manual/systemproperties.adoc#log4j.loggerContext.selector[`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 0e632f0

Please sign in to comment.