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.IS_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 77 private Log() { 78 } 79 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 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 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 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 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 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 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 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 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 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 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 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 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 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 181 public static void setSessionContext(Context context) { 182 getSessionManager().setContext(context); 183 } 184 185 public static void startSession(String shortMethodName) { 186 getSessionManager().startSession(shortMethodName, null); 187 } 188 189 public static void startSession(Session.Info info, String shortMethodName) { 190 getSessionManager().startSession(info, shortMethodName, null); 191 } 192 193 public static void startSession(String shortMethodName, String callerIdentification) { 194 getSessionManager().startSession(shortMethodName, callerIdentification); 195 } 196 197 public static void startSession(Session.Info info, String shortMethodName, 198 String callerIdentification) { 199 getSessionManager().startSession(info, shortMethodName, callerIdentification); 200 } 201 202 public static Session createSubsession() { 203 return getSessionManager().createSubsession(); 204 } 205 206 public static Session.Info getExternalSession() { 207 return getSessionManager().getExternalSession(); 208 } 209 210 public static void cancelSubsession(Session subsession) { 211 getSessionManager().cancelSubsession(subsession); 212 } 213 214 public static void continueSession(Session subsession, String shortMethodName) { 215 getSessionManager().continueSession(subsession, shortMethodName); 216 } 217 218 public static void endSession() { 219 getSessionManager().endSession(); 220 } 221 222 public static void registerSessionListener(SessionManager.ISessionListener l) { 223 getSessionManager().registerSessionListener(l); 224 } 225 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 238 public static void addEvent(EventManager.Loggable recordEntry, String event) { 239 getEventManager().event(recordEntry, event, null); 240 } 241 242 public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) { 243 getEventManager().event(recordEntry, event, data); 244 } 245 246 public static void addEvent(EventManager.Loggable recordEntry, String event, String format, 247 Object... args) { 248 getEventManager().event(recordEntry, event, format, args); 249 } 250 251 public static void registerEventListener(EventManager.EventListener e) { 252 getEventManager().registerEventListener(e); 253 } 254 255 public static void addRequestResponsePair(EventManager.TimedEventPair p) { 256 getEventManager().addRequestResponsePair(p); 257 } 258 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 * Dumps the events in a timeline format. 273 * @param pw The {@link IndentingPrintWriter} to write to. 274 * @hide 275 */ 276 public static void dumpEventsTimeline(IndentingPrintWriter pw) { 277 // If the Events logger has not been initialized, then there have been no events logged. 278 // Don't load it now! 279 synchronized (sSingletonSync) { 280 if (sEventManager != null) { 281 getEventManager().dumpEventsTimeline(pw); 282 } else { 283 pw.println("No Historical Events Logged."); 284 } 285 } 286 } 287 288 /** 289 * Enable or disable extended telecom logging. 290 * 291 * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled, 292 * {@code false} if it should be disabled. 293 */ 294 public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) { 295 // If the state hasn't changed, bail early. 296 if (sIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) { 297 return; 298 } 299 300 if (sEventManager != null) { 301 sEventManager.changeEventCacheSize(isExtendedLoggingEnabled ? 302 EVENTS_TO_CACHE_DEBUG : EVENTS_TO_CACHE); 303 } 304 305 sIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled; 306 if (sIsUserExtendedLoggingEnabled) { 307 sUserExtendedLoggingStopTime = System.currentTimeMillis() 308 + EXTENDED_LOGGING_DURATION_MILLIS; 309 } else { 310 sUserExtendedLoggingStopTime = 0; 311 } 312 } 313 314 private static EventManager getEventManager() { 315 // Checking for null again outside of synchronization because we only need to synchronize 316 // during the lazy loading of the events logger. We don't need to synchronize elsewhere. 317 if (sEventManager == null) { 318 synchronized (sSingletonSync) { 319 if (sEventManager == null) { 320 sEventManager = new EventManager(Log::getSessionId); 321 return sEventManager; 322 } 323 } 324 } 325 return sEventManager; 326 } 327 328 @VisibleForTesting 329 public static SessionManager getSessionManager() { 330 // Checking for null again outside of synchronization because we only need to synchronize 331 // during the lazy loading of the session logger. We don't need to synchronize elsewhere. 332 if (sSessionManager == null) { 333 synchronized (sSingletonSync) { 334 if (sSessionManager == null) { 335 sSessionManager = new SessionManager(); 336 return sSessionManager; 337 } 338 } 339 } 340 return sSessionManager; 341 } 342 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 */ 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 367 public static boolean isLoggable(int level) { 368 return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); 369 } 370 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 "***" otherwise. 411 */ 412 public static String pii(Object pii) { 413 if (pii == null || VERBOSE) { 414 return String.valueOf(pii); 415 } 416 return "***"; 417 } 418 419 private static String getPrefixFromObject(Object obj) { 420 return obj == null ? "<null>" : obj.getClass().getSimpleName(); 421 } 422 423 private static String buildMessage(String prefix, String format, Object... args) { 424 // Incorporate thread ID and calling method into prefix 425 String sessionName = getSessionId(); 426 String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName; 427 428 String msg; 429 try { 430 msg = (args == null || args.length == 0) ? format 431 : String.format(Locale.US, format, args); 432 } catch (IllegalFormatException ife) { 433 e(TAG, ife, "Log: IllegalFormatException: formatString='%s' numArgs=%d", format, 434 args.length); 435 msg = format + " (An error occurred while formatting the message.)"; 436 } 437 return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix); 438 } 439 } 440