• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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