1 /* 2 * Copyright 2014, 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 17 package android.telecom; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.os.Build; 23 import android.telecom.Logging.EventManager; 24 import android.telecom.Logging.Session; 25 import android.telecom.Logging.SessionManager; 26 import android.telephony.PhoneNumberUtils; 27 import android.text.TextUtils; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.IndentingPrintWriter; 31 32 import java.util.IllegalFormatException; 33 import java.util.Locale; 34 35 /** 36 * Manages logging for the entire module. 37 * 38 * @hide 39 */ 40 public class Log { 41 42 private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes 43 44 private static final int EVENTS_TO_CACHE = 10; 45 private static final int EVENTS_TO_CACHE_DEBUG = 20; 46 47 /** 48 * When generating a bug report, include the last X dialable digits when logging phone numbers. 49 */ 50 private static final int NUM_DIALABLE_DIGITS_TO_LOG = Build.IS_USER ? 0 : 2; 51 52 // Generic tag for all Telecom logging 53 @VisibleForTesting 54 public static String TAG = "TelecomFramework"; 55 public static boolean DEBUG = isLoggable(android.util.Log.DEBUG); 56 public static boolean INFO = isLoggable(android.util.Log.INFO); 57 public static boolean VERBOSE = isLoggable(android.util.Log.VERBOSE); 58 public static boolean WARN = isLoggable(android.util.Log.WARN); 59 public static boolean ERROR = isLoggable(android.util.Log.ERROR); 60 61 private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */ 62 private static final boolean USER_BUILD = Build.IS_USER; 63 64 // Used to synchronize singleton logging lazy initialization 65 private static final Object sSingletonSync = new Object(); 66 private static EventManager sEventManager; 67 private static SessionManager sSessionManager; 68 69 /** 70 * Tracks whether user-activated extended logging is enabled. 71 */ 72 private static boolean sIsUserExtendedLoggingEnabled = false; 73 74 /** 75 * The time when user-activated extended logging should be ended. Used to determine when 76 * extended logging should automatically be disabled. 77 */ 78 private static long sUserExtendedLoggingStopTime = 0; 79 Log()80 private Log() { 81 } 82 d(String prefix, String format, Object... args)83 public static void d(String prefix, String format, Object... args) { 84 if (sIsUserExtendedLoggingEnabled) { 85 maybeDisableLogging(); 86 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 87 } else if (DEBUG) { 88 android.util.Slog.d(TAG, buildMessage(prefix, format, args)); 89 } 90 } 91 d(Object objectPrefix, String format, Object... args)92 public static void d(Object objectPrefix, String format, Object... args) { 93 if (sIsUserExtendedLoggingEnabled) { 94 maybeDisableLogging(); 95 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 96 } else if (DEBUG) { 97 android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 98 } 99 } 100 101 @UnsupportedAppUsage i(String prefix, String format, Object... args)102 public static void i(String prefix, String format, Object... args) { 103 if (INFO) { 104 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 105 } 106 } 107 i(Object objectPrefix, String format, Object... args)108 public static void i(Object objectPrefix, String format, Object... args) { 109 if (INFO) { 110 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 111 } 112 } 113 v(String prefix, String format, Object... args)114 public static void v(String prefix, String format, Object... args) { 115 if (sIsUserExtendedLoggingEnabled) { 116 maybeDisableLogging(); 117 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 118 } else if (VERBOSE) { 119 android.util.Slog.v(TAG, buildMessage(prefix, format, args)); 120 } 121 } 122 v(Object objectPrefix, String format, Object... args)123 public static void v(Object objectPrefix, String format, Object... args) { 124 if (sIsUserExtendedLoggingEnabled) { 125 maybeDisableLogging(); 126 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 127 } else if (VERBOSE) { 128 android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 129 } 130 } 131 132 @UnsupportedAppUsage w(String prefix, String format, Object... args)133 public static void w(String prefix, String format, Object... args) { 134 if (WARN) { 135 android.util.Slog.w(TAG, buildMessage(prefix, format, args)); 136 } 137 } 138 w(Object objectPrefix, String format, Object... args)139 public static void w(Object objectPrefix, String format, Object... args) { 140 if (WARN) { 141 android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 142 } 143 } 144 e(String prefix, Throwable tr, String format, Object... args)145 public static void e(String prefix, Throwable tr, String format, Object... args) { 146 if (ERROR) { 147 android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr); 148 } 149 } 150 e(Object objectPrefix, Throwable tr, String format, Object... args)151 public static void e(Object objectPrefix, Throwable tr, String format, Object... args) { 152 if (ERROR) { 153 android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 154 tr); 155 } 156 } 157 wtf(String prefix, Throwable tr, String format, Object... args)158 public static void wtf(String prefix, Throwable tr, String format, Object... args) { 159 android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr); 160 } 161 wtf(Object objectPrefix, Throwable tr, String format, Object... args)162 public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) { 163 android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 164 tr); 165 } 166 wtf(String prefix, String format, Object... args)167 public static void wtf(String prefix, String format, Object... args) { 168 String msg = buildMessage(prefix, format, args); 169 android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg)); 170 } 171 wtf(Object objectPrefix, String format, Object... args)172 public static void wtf(Object objectPrefix, String format, Object... args) { 173 String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args); 174 android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg)); 175 } 176 177 /** 178 * The ease of use methods below only act mostly as proxies to the Session and Event Loggers. 179 * They also control the lazy loaders of the singleton instances, which will never be loaded if 180 * the proxy methods aren't used. 181 * 182 * Please see each method's documentation inside of their respective implementations in the 183 * loggers. 184 */ 185 setSessionContext(Context context)186 public static void setSessionContext(Context context) { 187 getSessionManager().setContext(context); 188 } 189 startSession(String shortMethodName)190 public static void startSession(String shortMethodName) { 191 getSessionManager().startSession(shortMethodName, null); 192 } 193 startSession(Session.Info info, String shortMethodName)194 public static void startSession(Session.Info info, String shortMethodName) { 195 getSessionManager().startSession(info, shortMethodName, null); 196 } 197 startSession(String shortMethodName, String callerIdentification)198 public static void startSession(String shortMethodName, String callerIdentification) { 199 getSessionManager().startSession(shortMethodName, callerIdentification); 200 } 201 startSession(Session.Info info, String shortMethodName, String callerIdentification)202 public static void startSession(Session.Info info, String shortMethodName, 203 String callerIdentification) { 204 getSessionManager().startSession(info, shortMethodName, callerIdentification); 205 } 206 createSubsession()207 public static Session createSubsession() { 208 return getSessionManager().createSubsession(); 209 } 210 getExternalSession()211 public static Session.Info getExternalSession() { 212 return getSessionManager().getExternalSession(); 213 } 214 cancelSubsession(Session subsession)215 public static void cancelSubsession(Session subsession) { 216 getSessionManager().cancelSubsession(subsession); 217 } 218 continueSession(Session subsession, String shortMethodName)219 public static void continueSession(Session subsession, String shortMethodName) { 220 getSessionManager().continueSession(subsession, shortMethodName); 221 } 222 endSession()223 public static void endSession() { 224 getSessionManager().endSession(); 225 } 226 registerSessionListener(SessionManager.ISessionListener l)227 public static void registerSessionListener(SessionManager.ISessionListener l) { 228 getSessionManager().registerSessionListener(l); 229 } 230 getSessionId()231 public static String getSessionId() { 232 // If the Session logger has not been initialized, then there have been no sessions logged. 233 // Don't load it now! 234 synchronized (sSingletonSync) { 235 if (sSessionManager != null) { 236 return getSessionManager().getSessionId(); 237 } else { 238 return ""; 239 } 240 } 241 } 242 addEvent(EventManager.Loggable recordEntry, String event)243 public static void addEvent(EventManager.Loggable recordEntry, String event) { 244 getEventManager().event(recordEntry, event, null); 245 } 246 addEvent(EventManager.Loggable recordEntry, String event, Object data)247 public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) { 248 getEventManager().event(recordEntry, event, data); 249 } 250 addEvent(EventManager.Loggable recordEntry, String event, String format, Object... args)251 public static void addEvent(EventManager.Loggable recordEntry, String event, String format, 252 Object... args) { 253 getEventManager().event(recordEntry, event, format, args); 254 } 255 registerEventListener(EventManager.EventListener e)256 public static void registerEventListener(EventManager.EventListener e) { 257 getEventManager().registerEventListener(e); 258 } 259 addRequestResponsePair(EventManager.TimedEventPair p)260 public static void addRequestResponsePair(EventManager.TimedEventPair p) { 261 getEventManager().addRequestResponsePair(p); 262 } 263 dumpEvents(IndentingPrintWriter pw)264 public static void dumpEvents(IndentingPrintWriter pw) { 265 // If the Events logger has not been initialized, then there have been no events logged. 266 // Don't load it now! 267 synchronized (sSingletonSync) { 268 if (sEventManager != null) { 269 getEventManager().dumpEvents(pw); 270 } else { 271 pw.println("No Historical Events Logged."); 272 } 273 } 274 } 275 276 /** 277 * Dumps the events in a timeline format. 278 * @param pw The {@link IndentingPrintWriter} to write to. 279 * @hide 280 */ dumpEventsTimeline(IndentingPrintWriter pw)281 public static void dumpEventsTimeline(IndentingPrintWriter pw) { 282 // If the Events logger has not been initialized, then there have been no events logged. 283 // Don't load it now! 284 synchronized (sSingletonSync) { 285 if (sEventManager != null) { 286 getEventManager().dumpEventsTimeline(pw); 287 } else { 288 pw.println("No Historical Events Logged."); 289 } 290 } 291 } 292 293 /** 294 * Enable or disable extended telecom logging. 295 * 296 * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled, 297 * {@code false} if it should be disabled. 298 */ setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled)299 public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) { 300 // If the state hasn't changed, bail early. 301 if (sIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) { 302 return; 303 } 304 305 if (sEventManager != null) { 306 sEventManager.changeEventCacheSize(isExtendedLoggingEnabled ? 307 EVENTS_TO_CACHE_DEBUG : EVENTS_TO_CACHE); 308 } 309 310 sIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled; 311 if (sIsUserExtendedLoggingEnabled) { 312 sUserExtendedLoggingStopTime = System.currentTimeMillis() 313 + EXTENDED_LOGGING_DURATION_MILLIS; 314 } else { 315 sUserExtendedLoggingStopTime = 0; 316 } 317 } 318 getEventManager()319 private static EventManager getEventManager() { 320 // Checking for null again outside of synchronization because we only need to synchronize 321 // during the lazy loading of the events logger. We don't need to synchronize elsewhere. 322 if (sEventManager == null) { 323 synchronized (sSingletonSync) { 324 if (sEventManager == null) { 325 sEventManager = new EventManager(Log::getSessionId); 326 return sEventManager; 327 } 328 } 329 } 330 return sEventManager; 331 } 332 333 @VisibleForTesting getSessionManager()334 public static SessionManager getSessionManager() { 335 // Checking for null again outside of synchronization because we only need to synchronize 336 // during the lazy loading of the session logger. We don't need to synchronize elsewhere. 337 if (sSessionManager == null) { 338 synchronized (sSingletonSync) { 339 if (sSessionManager == null) { 340 sSessionManager = new SessionManager(); 341 return sSessionManager; 342 } 343 } 344 } 345 return sSessionManager; 346 } 347 setTag(String tag)348 public static void setTag(String tag) { 349 TAG = tag; 350 DEBUG = isLoggable(android.util.Log.DEBUG); 351 INFO = isLoggable(android.util.Log.INFO); 352 VERBOSE = isLoggable(android.util.Log.VERBOSE); 353 WARN = isLoggable(android.util.Log.WARN); 354 ERROR = isLoggable(android.util.Log.ERROR); 355 } 356 357 /** 358 * If user enabled extended logging is enabled and the time limit has passed, disables the 359 * extended logging. 360 */ maybeDisableLogging()361 private static void maybeDisableLogging() { 362 if (!sIsUserExtendedLoggingEnabled) { 363 return; 364 } 365 366 if (sUserExtendedLoggingStopTime < System.currentTimeMillis()) { 367 sUserExtendedLoggingStopTime = 0; 368 sIsUserExtendedLoggingEnabled = false; 369 } 370 } 371 isLoggable(int level)372 public static boolean isLoggable(int level) { 373 return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); 374 } 375 376 /** 377 * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone 378 * phone number in {@link String} format. 379 * @param pii The information to obfuscate. 380 * @return The obfuscated string. 381 */ piiHandle(Object pii)382 public static String piiHandle(Object pii) { 383 if (pii == null || VERBOSE) { 384 return String.valueOf(pii); 385 } 386 387 StringBuilder sb = new StringBuilder(); 388 if (pii instanceof Uri) { 389 Uri uri = (Uri) pii; 390 String scheme = uri.getScheme(); 391 392 if (!TextUtils.isEmpty(scheme)) { 393 sb.append(scheme).append(":"); 394 } 395 396 String textToObfuscate = uri.getSchemeSpecificPart(); 397 if (PhoneAccount.SCHEME_TEL.equals(scheme)) { 398 obfuscatePhoneNumber(sb, textToObfuscate); 399 } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 400 for (int i = 0; i < textToObfuscate.length(); i++) { 401 char c = textToObfuscate.charAt(i); 402 if (c != '@' && c != '.') { 403 c = '*'; 404 } 405 sb.append(c); 406 } 407 } else { 408 sb.append(pii(pii)); 409 } 410 } else if (pii instanceof String) { 411 String number = (String) pii; 412 obfuscatePhoneNumber(sb, number); 413 } 414 415 return sb.toString(); 416 } 417 418 /** 419 * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the 420 * phone number. 421 * @param sb String buffer to write obfuscated number to. 422 * @param phoneNumber The number to obfuscate. 423 */ obfuscatePhoneNumber(StringBuilder sb, String phoneNumber)424 private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) { 425 int numDigitsToObfuscate = getDialableCount(phoneNumber) 426 - NUM_DIALABLE_DIGITS_TO_LOG; 427 for (int i = 0; i < phoneNumber.length(); i++) { 428 char c = phoneNumber.charAt(i); 429 boolean isDialable = PhoneNumberUtils.isDialable(c); 430 if (isDialable) { 431 numDigitsToObfuscate--; 432 } 433 sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c); 434 } 435 } 436 437 /** 438 * Determines the number of dialable characters in a string. 439 * @param toCount The string to count dialable characters in. 440 * @return The count of dialable characters. 441 */ getDialableCount(String toCount)442 private static int getDialableCount(String toCount) { 443 int numDialable = 0; 444 for (char c : toCount.toCharArray()) { 445 if (PhoneNumberUtils.isDialable(c)) { 446 numDialable++; 447 } 448 } 449 return numDialable; 450 } 451 452 /** 453 * Redact personally identifiable information for production users. 454 * If we are running in verbose mode, return the original string, 455 * and return "***" otherwise. 456 */ pii(Object pii)457 public static String pii(Object pii) { 458 if (pii == null || VERBOSE) { 459 return String.valueOf(pii); 460 } 461 return "***"; 462 } 463 getPrefixFromObject(Object obj)464 private static String getPrefixFromObject(Object obj) { 465 return obj == null ? "<null>" : obj.getClass().getSimpleName(); 466 } 467 buildMessage(String prefix, String format, Object... args)468 private static String buildMessage(String prefix, String format, Object... args) { 469 // Incorporate thread ID and calling method into prefix 470 String sessionName = getSessionId(); 471 String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName; 472 473 String msg; 474 try { 475 msg = (args == null || args.length == 0) ? format 476 : String.format(Locale.US, format, args); 477 } catch (IllegalFormatException ife) { 478 e(TAG, ife, "Log: IllegalFormatException: formatString='%s' numArgs=%d", format, 479 args.length); 480 msg = format + " (An error occurred while formatting the message.)"; 481 } 482 return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix); 483 } 484 } 485