blob: 47eff570b36877e73a744140ecd1d558df9e9120 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.networkrecommendation.util;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.Locale;
/**
* Wrapper for {@link Log} which adds the calling class/method to logged items.
*
* This works by traversing up the call stack and finding the first calling class whose name does
* not end with "Log" (allowing clients to add another layer such as AppLog when a constant tag is
* desired across the application).
*/
public final class Blog {
private static final String TAG = "Blog";
private Blog() {}
public static void i(String tag, String format, Object... args) {
Log.i(tag, buildMessage(format, args));
}
public static void i(String tag, Throwable tr, String format, Object... args) {
Log.i(tag, buildMessage(format, args), tr);
}
public static void v(String tag, String format, Object... args) {
// Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
// release builds.
Log.v(tag, buildMessage(format, args));
}
public static void v(String tag, Throwable tr, String format, Object... args) {
// Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
// release builds.
Log.v(tag, buildMessage(format, args), tr);
}
public static void d(String tag, String format, Object... args) {
if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, buildMessage(format, args));
}
}
public static void d(String tag, Throwable tr, String format, Object... args) {
if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, buildMessage(format, args), tr);
}
}
public static void w(String tag, Throwable tr, String format, Object... args) {
Log.w(tag, buildMessage(format, args), tr);
}
public static void w(String tag, String format, Object... args) {
Log.w(tag, buildMessage(format, args));
}
public static void e(String tag, String format, Object... args) {
Log.e(tag, buildMessage(format, args));
}
public static void e(String tag, Throwable tr, String format, Object... args) {
Log.e(tag, buildMessage(format, args), tr);
}
public static void wtf(String tag, String format, Object... args) {
// Ensure we always log a stack trace when calling Log.wtf.
Log.wtf(tag, buildMessage(format, args), new WhatATerribleException());
}
public static void wtf(String tag, Throwable tr, String format, Object...args) {
Log.wtf(tag, buildMessage(format, args), new WhatATerribleException(tr));
}
/**
* Redact personally identifiable information for production users.
*
* If:
* 1) String is null
* 2) String is empty
* 3) enableSensitiveLogging param passed in is true
* Return the String value of the input object.
* Else:
* Return a SHA-1 hash of the String value of the input object.
*/
public static String pii(Object pii, boolean enableSensitiveLogging) {
String val = String.valueOf(pii);
if (pii == null || TextUtils.isEmpty(val) || enableSensitiveLogging) {
return val;
}
return "[" + secureHash(val.getBytes()) + "]";
}
/**
* Returns a secure hash (using the SHA1 algorithm) of the provided input.
*
* @return the hash
* @param input the bytes for which the secure hash should be computed.
*/
public static String secureHash(byte[] input) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
return null;
}
messageDigest.update(input);
byte[] result = messageDigest.digest();
return Base64.encodeToString(
result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
}
/**
* Formats the caller's provided message and prepends useful info like
* calling thread ID and method name.
*/
private static String buildMessage(String format, Object... args) {
String msg;
try {
msg = (args == null || args.length == 0) ? format
: String.format(Locale.US, format, args);
} catch (IllegalFormatException ife) {
String formattedArgs = Arrays.toString(args);
wtf(TAG, ife, "msg: \"%s\" args: %s", format, formattedArgs);
msg = format + " " + formattedArgs;
}
StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
String caller = "<unknown>";
// Walk up the stack looking for the first caller outside of Blog whose name doesn't end
// with "Log". It will be at least two frames up, so start there.
for (int i = 2; i < trace.length; i++) {
String callingClass = trace[i].getClassName();
if (!callingClass.equals(Blog.class.getName())
&& !callingClass.endsWith("Log")) {
callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
caller = callingClass + "." + trace[i].getMethodName();
break;
}
}
return String.format(Locale.US, "[%d] %s: %s",
Thread.currentThread().getId(), caller, msg);
}
/** Named exception thrown when calling wtf(). */
public static class WhatATerribleException extends RuntimeException {
public WhatATerribleException() {
super();
}
public WhatATerribleException(Throwable t) {
super(t);
}
}
}