1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.util; 17 18 import android.annotation.Nullable; 19 import android.util.Log.Level; 20 21 import com.android.internal.annotations.GuardedBy; 22 import com.android.internal.os.RuntimeInit; 23 import com.android.ravenwood.RavenwoodRuntimeNative; 24 25 import java.io.PrintStream; 26 import java.text.SimpleDateFormat; 27 import java.util.Date; 28 import java.util.HashMap; 29 import java.util.Locale; 30 import java.util.Map; 31 32 /** 33 * Ravenwood "native substitution" class for {@link android.util.Log}. 34 * 35 * {@link android.util.Log} already uses the actual native code and doesn't use this class. 36 * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass 37 * annotation on {@link android.util.Log}. 38 */ 39 public class Log_ravenwood { 40 41 private static final SimpleDateFormat sTimestampFormat = 42 new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US); 43 44 private static final Object sLock = new Object(); 45 46 @GuardedBy("sLock") 47 private static int sDefaultLogLevel; 48 49 @GuardedBy("sLock") 50 private static final Map<String, Integer> sTagLogLevels = new HashMap<>(); 51 52 /** 53 * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setAndroidLogTags(String)} 54 * via reflections. 55 */ setLogLevels(String androidLogTags)56 public static void setLogLevels(String androidLogTags) { 57 var map = parseLogLevels(androidLogTags); 58 59 synchronized (sLock) { 60 sTagLogLevels.clear(); 61 sTagLogLevels.putAll(map); 62 63 var def = map.get("*"); 64 sDefaultLogLevel = def != null ? def : Log.VERBOSE; 65 } 66 } 67 parseLogLevels(String androidLogTags)68 private static Map<String, Integer> parseLogLevels(String androidLogTags) { 69 final Map<String, Integer> ret = new HashMap<>(); 70 71 if (androidLogTags == null) { 72 return ret; 73 } 74 75 String[] tagPairs = androidLogTags.trim().split("\\s+"); 76 for (String tagPair : tagPairs) { 77 String[] parts = tagPair.split(":"); 78 if (parts.length == 2) { 79 String tag = parts[0]; 80 try { 81 int priority = switch (parts[1].toUpperCase(Locale.ROOT)) { 82 case "V": yield Log.VERBOSE; 83 case "D": yield Log.DEBUG; 84 case "I": yield Log.INFO; 85 case "W": yield Log.WARN; 86 case "E": yield Log.ERROR; 87 case "F": yield Log.ERROR + 1; // Not used in the java side. 88 case "S": yield Integer.MAX_VALUE; // Silent 89 default: throw new IllegalArgumentException( 90 "Invalid priority level for tag: " + tag); 91 }; 92 93 ret.put(tag, priority); 94 } catch (IllegalArgumentException e) { 95 System.err.println(e.getMessage()); 96 } 97 } else { 98 System.err.println("Invalid tag format: " + tagPair); 99 } 100 } 101 102 return ret; 103 } 104 105 /** 106 * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setLogLevel(String, int)} 107 * via reflections. Pass NULL to {@code tag} to set the default level. 108 */ setLogLevel(@ullable String tag, int level)109 public static void setLogLevel(@Nullable String tag, int level) { 110 synchronized (sLock) { 111 if (tag == null) { 112 sDefaultLogLevel = level; 113 } else { 114 sTagLogLevels.put(tag, level); 115 } 116 } 117 } 118 119 /** 120 * Replaces {@link Log#isLoggable}. 121 */ isLoggable(String tag, @Level int priority)122 public static boolean isLoggable(String tag, @Level int priority) { 123 synchronized (sLock) { 124 var threshold = sTagLogLevels.get(tag); 125 if (threshold == null) { 126 threshold = sDefaultLogLevel; 127 } 128 return priority >= threshold; 129 } 130 } 131 println_native(int bufID, int priority, String tag, String msg)132 public static int println_native(int bufID, int priority, String tag, String msg) { 133 if (!isLoggable(tag, priority)) { 134 return msg.length(); 135 } 136 137 final String prio; 138 switch (priority) { 139 case Log.VERBOSE: prio = "V"; break; 140 case Log.DEBUG: prio = "D"; break; 141 case Log.INFO: prio = "I"; break; 142 case Log.WARN: prio = "W"; break; 143 case Log.ERROR: prio = "E"; break; 144 case Log.ASSERT: prio = "A"; break; 145 default: prio = "prio:" + priority; break; 146 } 147 148 String leading = sTimestampFormat.format(new Date()) 149 + " %-6d %-6d %s %-8s: ".formatted(getPid(), getTid(), prio, tag); 150 var out = getRealOut(); 151 for (String s : msg.split("\\n")) { 152 out.print(leading); 153 out.println(s); 154 } 155 return msg.length(); 156 } 157 logger_entry_max_payload_native()158 public static int logger_entry_max_payload_native() { 159 return 4068; // [ravenwood] This is what people use in various places. 160 } 161 162 /** 163 * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so 164 * that we don't end up in a recursive loop. 165 */ getRealOut()166 public static PrintStream getRealOut() { 167 if (RuntimeInit.sOut$ravenwood != null) { 168 return RuntimeInit.sOut$ravenwood; 169 } else { 170 return System.out; 171 } 172 } 173 174 /** 175 * PID. We need to use a JNI method to get it, but JNI isn't initially ready. 176 * Call {@link #onRavenwoodRuntimeNativeReady} to signal when JNI is ready, at which point 177 * we set this field. 178 * (We don't want to call native methods that may not be fully initialized even with a 179 * try-catch, because partially initialized JNI methods could crash the process.) 180 */ 181 private static volatile int sPid = 0; 182 183 private static ThreadLocal<Integer> sTid = 184 ThreadLocal.withInitial(RavenwoodRuntimeNative::gettid); 185 186 /** 187 * Call it when {@link RavenwoodRuntimeNative} is usable. 188 */ onRavenwoodRuntimeNativeReady()189 public static void onRavenwoodRuntimeNativeReady() { 190 sPid = RavenwoodRuntimeNative.getpid(); 191 } 192 getPid()193 private static int getPid() { 194 return sPid; 195 } 196 getTid()197 private static int getTid() { 198 if (sPid == 0) { 199 return 0; // Native methods not ready yet. 200 } 201 return sTid.get(); 202 } 203 } 204