1 /* 2 * Copyright (C) 2017 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 com.android.networkrecommendation.util; 17 18 import android.text.TextUtils; 19 import android.util.Base64; 20 import android.util.Log; 21 22 import java.security.MessageDigest; 23 import java.security.NoSuchAlgorithmException; 24 import java.util.Arrays; 25 import java.util.IllegalFormatException; 26 import java.util.Locale; 27 28 /** 29 * Wrapper for {@link Log} which adds the calling class/method to logged items. 30 * 31 * This works by traversing up the call stack and finding the first calling class whose name does 32 * not end with "Log" (allowing clients to add another layer such as AppLog when a constant tag is 33 * desired across the application). 34 */ 35 public final class Blog { 36 private static final String TAG = "Blog"; 37 Blog()38 private Blog() {} 39 i(String tag, String format, Object... args)40 public static void i(String tag, String format, Object... args) { 41 Log.i(tag, buildMessage(format, args)); 42 } 43 i(String tag, Throwable tr, String format, Object... args)44 public static void i(String tag, Throwable tr, String format, Object... args) { 45 Log.i(tag, buildMessage(format, args), tr); 46 } 47 v(String tag, String format, Object... args)48 public static void v(String tag, String format, Object... args) { 49 // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for 50 // release builds. 51 Log.v(tag, buildMessage(format, args)); 52 } 53 v(String tag, Throwable tr, String format, Object... args)54 public static void v(String tag, Throwable tr, String format, Object... args) { 55 // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for 56 // release builds. 57 Log.v(tag, buildMessage(format, args), tr); 58 } 59 d(String tag, String format, Object... args)60 public static void d(String tag, String format, Object... args) { 61 if (Log.isLoggable(tag, Log.DEBUG)) { 62 Log.d(tag, buildMessage(format, args)); 63 } 64 } 65 d(String tag, Throwable tr, String format, Object... args)66 public static void d(String tag, Throwable tr, String format, Object... args) { 67 if (Log.isLoggable(tag, Log.DEBUG)) { 68 Log.d(tag, buildMessage(format, args), tr); 69 } 70 } 71 w(String tag, Throwable tr, String format, Object... args)72 public static void w(String tag, Throwable tr, String format, Object... args) { 73 Log.w(tag, buildMessage(format, args), tr); 74 } 75 w(String tag, String format, Object... args)76 public static void w(String tag, String format, Object... args) { 77 Log.w(tag, buildMessage(format, args)); 78 } 79 e(String tag, String format, Object... args)80 public static void e(String tag, String format, Object... args) { 81 Log.e(tag, buildMessage(format, args)); 82 } 83 e(String tag, Throwable tr, String format, Object... args)84 public static void e(String tag, Throwable tr, String format, Object... args) { 85 Log.e(tag, buildMessage(format, args), tr); 86 } 87 wtf(String tag, String format, Object... args)88 public static void wtf(String tag, String format, Object... args) { 89 // Ensure we always log a stack trace when calling Log.wtf. 90 Log.wtf(tag, buildMessage(format, args), new WhatATerribleException()); 91 } 92 wtf(String tag, Throwable tr, String format, Object...args)93 public static void wtf(String tag, Throwable tr, String format, Object...args) { 94 Log.wtf(tag, buildMessage(format, args), new WhatATerribleException(tr)); 95 } 96 97 /** 98 * Redact personally identifiable information for production users. 99 * 100 * If: 101 * 1) String is null 102 * 2) String is empty 103 * 3) enableSensitiveLogging param passed in is true 104 * Return the String value of the input object. 105 * Else: 106 * Return a SHA-1 hash of the String value of the input object. 107 */ pii(Object pii, boolean enableSensitiveLogging)108 public static String pii(Object pii, boolean enableSensitiveLogging) { 109 String val = String.valueOf(pii); 110 if (pii == null || TextUtils.isEmpty(val) || enableSensitiveLogging) { 111 return val; 112 } 113 return "[" + secureHash(val.getBytes()) + "]"; 114 } 115 116 /** 117 * Returns a secure hash (using the SHA1 algorithm) of the provided input. 118 * 119 * @return the hash 120 * @param input the bytes for which the secure hash should be computed. 121 */ secureHash(byte[] input)122 public static String secureHash(byte[] input) { 123 MessageDigest messageDigest; 124 try { 125 messageDigest = MessageDigest.getInstance("SHA-1"); 126 } catch (NoSuchAlgorithmException e) { 127 return null; 128 } 129 messageDigest.update(input); 130 byte[] result = messageDigest.digest(); 131 return Base64.encodeToString( 132 result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); 133 } 134 135 /** 136 * Formats the caller's provided message and prepends useful info like 137 * calling thread ID and method name. 138 */ buildMessage(String format, Object... args)139 private static String buildMessage(String format, Object... args) { 140 String msg; 141 try { 142 msg = (args == null || args.length == 0) ? format 143 : String.format(Locale.US, format, args); 144 } catch (IllegalFormatException ife) { 145 String formattedArgs = Arrays.toString(args); 146 wtf(TAG, ife, "msg: \"%s\" args: %s", format, formattedArgs); 147 msg = format + " " + formattedArgs; 148 } 149 StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace(); 150 String caller = "<unknown>"; 151 // Walk up the stack looking for the first caller outside of Blog whose name doesn't end 152 // with "Log". It will be at least two frames up, so start there. 153 for (int i = 2; i < trace.length; i++) { 154 String callingClass = trace[i].getClassName(); 155 if (!callingClass.equals(Blog.class.getName()) 156 && !callingClass.endsWith("Log")) { 157 callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1); 158 callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1); 159 caller = callingClass + "." + trace[i].getMethodName(); 160 break; 161 } 162 } 163 return String.format(Locale.US, "[%d] %s: %s", 164 Thread.currentThread().getId(), caller, msg); 165 } 166 167 /** Named exception thrown when calling wtf(). */ 168 public static class WhatATerribleException extends RuntimeException { WhatATerribleException()169 public WhatATerribleException() { 170 super(); 171 } WhatATerribleException(Throwable t)172 public WhatATerribleException(Throwable t) { 173 super(t); 174 } 175 } 176 } 177