Home | History | Annotate | Download | only in telecom
      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.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 
     80     private Log() {
     81     }
     82 
     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 
     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
    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 
    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 
    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 
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    186     public static void setSessionContext(Context context) {
    187         getSessionManager().setContext(context);
    188     }
    189 
    190     public static void startSession(String shortMethodName) {
    191         getSessionManager().startSession(shortMethodName, null);
    192     }
    193 
    194     public static void startSession(Session.Info info, String shortMethodName) {
    195         getSessionManager().startSession(info, shortMethodName, null);
    196     }
    197 
    198     public static void startSession(String shortMethodName, String callerIdentification) {
    199         getSessionManager().startSession(shortMethodName, callerIdentification);
    200     }
    201 
    202     public static void startSession(Session.Info info, String shortMethodName,
    203             String callerIdentification) {
    204         getSessionManager().startSession(info, shortMethodName, callerIdentification);
    205     }
    206 
    207     public static Session createSubsession() {
    208         return getSessionManager().createSubsession();
    209     }
    210 
    211     public static Session.Info getExternalSession() {
    212         return getSessionManager().getExternalSession();
    213     }
    214 
    215     public static void cancelSubsession(Session subsession) {
    216         getSessionManager().cancelSubsession(subsession);
    217     }
    218 
    219     public static void continueSession(Session subsession, String shortMethodName) {
    220         getSessionManager().continueSession(subsession, shortMethodName);
    221     }
    222 
    223     public static void endSession() {
    224         getSessionManager().endSession();
    225     }
    226 
    227     public static void registerSessionListener(SessionManager.ISessionListener l) {
    228         getSessionManager().registerSessionListener(l);
    229     }
    230 
    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 
    243     public static void addEvent(EventManager.Loggable recordEntry, String event) {
    244         getEventManager().event(recordEntry, event, null);
    245     }
    246 
    247     public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) {
    248         getEventManager().event(recordEntry, event, data);
    249     }
    250 
    251     public static void addEvent(EventManager.Loggable recordEntry, String event, String format,
    252             Object... args) {
    253         getEventManager().event(recordEntry, event, format, args);
    254     }
    255 
    256     public static void registerEventListener(EventManager.EventListener e) {
    257         getEventManager().registerEventListener(e);
    258     }
    259 
    260     public static void addRequestResponsePair(EventManager.TimedEventPair p) {
    261         getEventManager().addRequestResponsePair(p);
    262     }
    263 
    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      */
    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      */
    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 
    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
    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 
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    457     public static String pii(Object pii) {
    458         if (pii == null || VERBOSE) {
    459             return String.valueOf(pii);
    460         }
    461         return "***";
    462     }
    463 
    464     private static String getPrefixFromObject(Object obj) {
    465         return obj == null ? "<null>" : obj.getClass().getSimpleName();
    466     }
    467 
    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