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