Add a way to configure log levels
Bug: 381112373
Bug: 380949304
Bug: 380938496
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s
Flag: EXEMPT host test change only
Change-Id: I3851ba55abf8cdbdf518dda83b4780f2128c013d
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 172cec3..3d2bb8e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -105,6 +105,9 @@
private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
+ private static final String ANDROID_LOG_TAGS = "ANDROID_LOG_TAGS";
+ private static final String RAVENWOOD_ANDROID_LOG_TAGS = "RAVENWOOD_" + ANDROID_LOG_TAGS;
+
/**
* When enabled, attempt to dump all thread stacks just before we hit the
* overall Tradefed timeout, to aid in debugging deadlocks.
@@ -232,20 +235,22 @@
// Make sure libravenwood_runtime is loaded.
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
+ Log_ravenwood.setLogLevels(getLogTags());
Log_ravenwood.onRavenwoodRuntimeNativeReady();
// Do the basic set up for the android sysprops.
RavenwoodSystemProperties.initialize();
+ // Enable all log levels for native logging, until we'll have a way to change the native
+ // side log level at runtime.
// Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
// before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
- if (RAVENWOOD_VERBOSE_LOGGING) {
- RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
- try {
- Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
- } catch (ErrnoException e) {
- throw new RuntimeException(e);
- }
+ // This would also prevent libbase from crashing the process (b/381112373) because
+ // the string format it accepts is very limited.
+ try {
+ Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
}
// Make sure libandroid_runtime is loaded.
@@ -333,6 +338,18 @@
initializeCompatIds();
}
+ /**
+ * Get log tags from environmental variable.
+ */
+ @Nullable
+ private static String getLogTags() {
+ var logTags = System.getenv(RAVENWOOD_ANDROID_LOG_TAGS);
+ if (logTags == null) {
+ logTags = System.getenv(ANDROID_LOG_TAGS);
+ }
+ return logTags;
+ }
+
private static void loadRavenwoodProperties() {
var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index e49d3d9..d4b15c6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -193,6 +193,12 @@
return IS_ON_RAVENWOOD;
}
+ private static void ensureOnRavenwood(String featureName) {
+ if (!IS_ON_RAVENWOOD) {
+ throw new RuntimeException(featureName + " is only supported on Ravenwood.");
+ }
+ }
+
/**
* @deprecated Use
* {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()}
@@ -242,6 +248,40 @@
return System.currentTimeMillis();
}
+ /**
+ * Equivalent to setting the ANDROID_LOG_TAGS environmental variable.
+ *
+ * See https://developer.android.com/tools/logcat#filteringOutput for the string format.
+ *
+ * NOTE: this works only on Ravenwood.
+ */
+ public static void setAndroidLogTags(@Nullable String androidLogTags) {
+ ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()");
+ try {
+ Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+ var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class);
+ setter.invoke(null, androidLogTags);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level.
+ *
+ * NOTE: this works only on Ravenwood.
+ */
+ public static void setLogLevel(@Nullable String tag, int level) {
+ ensureOnRavenwood("RavenwoodRule.setLogLevel()");
+ try {
+ Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+ var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class);
+ setter.invoke(null, tag, level);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
// Below are internal to ravenwood. Don't use them from normal tests...
public static class RavenwoodPrivate {
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
index 7b26fe5..96fa212 100644
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
@@ -15,8 +15,10 @@
*/
package android.util;
+import android.annotation.Nullable;
import android.util.Log.Level;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.common.RavenwoodCommonUtils;
@@ -24,7 +26,9 @@
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
/**
* Ravenwood "native substitution" class for {@link android.util.Log}.
@@ -35,16 +39,101 @@
*/
public class Log_ravenwood {
- public static final SimpleDateFormat sTimestampFormat =
+ private static final SimpleDateFormat sTimestampFormat =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
- public static boolean isLoggable(String tag, @Level int level) {
- return true;
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static int sDefaultLogLevel;
+
+ @GuardedBy("sLock")
+ private static final Map<String, Integer> sTagLogLevels = new HashMap<>();
+
+ /**
+ * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setAndroidLogTags(String)}
+ * via reflections.
+ */
+ public static void setLogLevels(String androidLogTags) {
+ var map = parseLogLevels(androidLogTags);
+
+ synchronized (sLock) {
+ sTagLogLevels.clear();
+ sTagLogLevels.putAll(map);
+
+ var def = map.get("*");
+ sDefaultLogLevel = def != null ? def
+ : RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING ? Log.VERBOSE : Log.INFO;
+ }
+ }
+
+ private static Map<String, Integer> parseLogLevels(String androidLogTags) {
+ final Map<String, Integer> ret = new HashMap<>();
+
+ if (androidLogTags == null) {
+ return ret;
+ }
+
+ String[] tagPairs = androidLogTags.trim().split("\\s+");
+ for (String tagPair : tagPairs) {
+ String[] parts = tagPair.split(":");
+ if (parts.length == 2) {
+ String tag = parts[0];
+ try {
+ int priority = switch (parts[1]) {
+ case "V": yield Log.VERBOSE;
+ case "D": yield Log.DEBUG;
+ case "I": yield Log.INFO;
+ case "W": yield Log.WARN;
+ case "E": yield Log.ERROR;
+ case "F": yield Log.ERROR + 1; // Not used in the java side.
+ case "S": yield Integer.MAX_VALUE; // Silent
+ default: throw new IllegalArgumentException(
+ "Invalid priority level for tag: " + tag);
+ };
+
+ ret.put(tag, priority);
+ } catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage());
+ }
+ } else {
+ System.err.println("Invalid tag format: " + tagPair);
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setLogLevel(String, int)}
+ * via reflections. Pass NULL to {@code tag} to set the default level.
+ */
+ public static void setLogLevel(@Nullable String tag, int level) {
+ synchronized (sLock) {
+ if (tag == null) {
+ sDefaultLogLevel = level;
+ } else {
+ sTagLogLevels.put(tag, level);
+ }
+ }
+ }
+
+ /**
+ * Replaces {@link Log#isLoggable}.
+ */
+ public static boolean isLoggable(String tag, @Level int priority) {
+ synchronized (sLock) {
+ var threshold = sTagLogLevels.get(tag);
+ if (threshold == null) {
+ threshold = sDefaultLogLevel;
+ }
+ return priority >= threshold;
+ }
}
public static int println_native(int bufID, int priority, String tag, String msg) {
- if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) {
- return msg.length(); // No verbose logging.
+ if (!isLoggable(tag, priority)) {
+ return msg.length();
}
final String prio;
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
new file mode 100644
index 0000000..7be562c
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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 com.android.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.Test;
+
+public class RavenwoodLogLevelTest {
+ /**
+ * Assert that the `priority` is loggable, but one level below is not.
+ */
+ private void assertBarelyLoggable(String tag, int priority) {
+ assertEquals(true, Log.isLoggable(tag, priority));
+ assertEquals(false, Log.isLoggable(tag, priority - 1));
+ }
+
+ @Test
+ public void testDefaultLogTags() {
+ RavenwoodRule.setAndroidLogTags(null);
+
+ // Info should always be loggable.
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ var verboseEnabled = RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+ assertEquals(verboseEnabled, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(verboseEnabled, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllVerbose() {
+ RavenwoodRule.setAndroidLogTags("*:V");
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllSilent() {
+ RavenwoodRule.setAndroidLogTags("*:S");
+
+ assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+ assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+ }
+
+ @Test
+ public void testComplex() {
+ RavenwoodRule.setAndroidLogTags("TAG1:W TAG2:D *:I");
+
+ assertBarelyLoggable("TAG1", Log.WARN);
+ assertBarelyLoggable("TAG2", Log.DEBUG);
+ assertBarelyLoggable("TAG3", Log.INFO);
+ }
+
+ @Test
+ public void testAllVerbose_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.VERBOSE);
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllSilent_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.ASSERT + 1);
+
+ assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+ assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+ }
+
+ @Test
+ public void testComplex_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.INFO);
+ RavenwoodRule.setLogLevel("TAG1", Log.WARN);
+ RavenwoodRule.setLogLevel("TAG2", Log.DEBUG);
+
+ assertBarelyLoggable("TAG1", Log.WARN);
+ assertBarelyLoggable("TAG2", Log.DEBUG);
+ assertBarelyLoggable("TAG3", Log.INFO);
+ }
+}