Last active
July 13, 2018 16:06
-
-
Save yesitskev/e93c09c6b5af3339a3cb to your computer and use it in GitHub Desktop.
Stetho Timber Logger
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.util.Log; | |
import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer; | |
import com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod; | |
import org.json.JSONObject; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import timber.log.Timber; | |
/** | |
* Log things to console (hacked job). | |
* | |
* This probably isn't too useful right now as Stetho is still very immature. The class name | |
* has to be called console or some other chrome handler name. For some reason Stetho is using | |
* the class name to bind to the corresponding chrome handle. | |
* | |
* Ideally, in the future Stetho should have a class annotation marking the handle to avoid this | |
* 1 to 1 relationship of handle binding. | |
*/ | |
public static class Console extends com.facebook.stetho.inspector.protocol.module.Console implements Timber.TaggedTree { | |
private static final int MAX_LOG_LENGTH = 4000; | |
private static final Pattern ANONYMOUS_CLASS = Pattern.compile("\\$\\d+$"); | |
private static final ThreadLocal<String> NEXT_TAG = new ThreadLocal<>(); | |
JsonRpcPeer peer; | |
public Console() { | |
// Empty. | |
} | |
@Override | |
@ChromeDevtoolsMethod | |
public void enable(JsonRpcPeer peer, JSONObject params) { | |
this.peer = peer; | |
} | |
@Override | |
@ChromeDevtoolsMethod | |
public void disable(JsonRpcPeer peer, JSONObject params) { | |
this.peer = null; | |
} | |
private static String maybeFormat(String message, Object... args) { | |
// If no varargs are supplied, treat it as a request to log the string without formatting. | |
return args.length == 0 ? message : String.format(message, args); | |
} | |
@Override public final void tag(String tag) { | |
NEXT_TAG.set(tag); | |
} | |
/** | |
* Returns an explicitly set tag for the next log message or {@code null}. Calling this method | |
* clears any set tag so it may only be called once. | |
*/ | |
protected final String nextTag() { | |
String tag = NEXT_TAG.get(); | |
if (tag != null) { | |
NEXT_TAG.remove(); | |
} | |
return tag; | |
} | |
/** | |
* Creates a tag for a log message. | |
* <p> | |
* By default this method will check {@link #nextTag()} for an explicit tag. If there is no | |
* explicit tag, the class name of the caller will be used by inspecting the stack trace of the | |
* current thread. | |
* <p> | |
* Note: Do not call {@code super.createTag()} if you override this method. It will produce | |
* incorrect results. | |
*/ | |
protected String createTag() { | |
String tag = nextTag(); | |
if (tag != null) { | |
return tag; | |
} | |
// DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass | |
// because Robolectric runs them on the JVM but on Android the elements are different. | |
StackTraceElement[] stackTrace = new Throwable().getStackTrace(); | |
if (stackTrace.length < 6) { | |
throw new IllegalStateException( | |
"Synthetic stacktrace didn't have enough elements: are you using proguard?"); | |
} | |
tag = stackTrace[5].getClassName(); | |
Matcher m = ANONYMOUS_CLASS.matcher(tag); | |
if (m.find()) { | |
tag = m.replaceAll(""); | |
} | |
return tag.substring(tag.lastIndexOf('.') + 1); | |
} | |
private void throwShade(MessageLevel priority, String message, Throwable t) { | |
if (message == null || message.length() == 0) { | |
if (t == null) { | |
return; // Swallow message if it's null and there's no throwable. | |
} | |
message = Log.getStackTraceString(t); | |
} else if (t != null) { | |
message += "\n" + Log.getStackTraceString(t); | |
} | |
String tag = createTag(); | |
logMessage(priority, tag, message); | |
} | |
/** Log a message! */ | |
protected void logMessage(MessageLevel priority, String tag, String message) { | |
if (peer == null) return; | |
Console.MessageAddedRequest request = new Console.MessageAddedRequest(); | |
Console.ConsoleMessage console = new Console.ConsoleMessage(); | |
request.message = console; | |
console.source = MessageSource.CONSOLE_API; | |
console.level = priority; | |
if (message.length() < MAX_LOG_LENGTH) { | |
console.text = String.format("[%s] %s", tag, message); | |
peer.invokeMethod("Console.messageAdded", request, null); | |
return; | |
} | |
// Split by line, then ensure each line can fit into Log's maximum length. | |
for (int i = 0, length = message.length(); i < length; i++) { | |
int newline = message.indexOf('\n', i); | |
newline = newline != -1 ? newline : length; | |
do { | |
int end = Math.min(newline, i + MAX_LOG_LENGTH); | |
console.text = String.format("[%s] %s", tag, message.substring(i, end)); | |
peer.invokeMethod("Console.messageAdded", request, null); | |
i = end; | |
} while (i < newline); | |
} | |
} | |
@Override public final void v(String message, Object... args) { | |
throwShade(MessageLevel.LOG, maybeFormat(message, args), null); | |
} | |
@Override public final void v(Throwable t, String message, Object... args) { | |
throwShade(MessageLevel.LOG, maybeFormat(message, args), t); | |
} | |
@Override public final void d(String message, Object... args) { | |
throwShade(MessageLevel.DEBUG, maybeFormat(message, args), null); | |
} | |
@Override public final void d(Throwable t, String message, Object... args) { | |
throwShade(MessageLevel.DEBUG, maybeFormat(message, args), t); | |
} | |
@Override public final void i(String message, Object... args) { | |
throwShade(MessageLevel.LOG, maybeFormat(message, args), null); | |
} | |
@Override public final void i(Throwable t, String message, Object... args) { | |
throwShade(MessageLevel.LOG, maybeFormat(message, args), t); | |
} | |
@Override public final void w(String message, Object... args) { | |
throwShade(MessageLevel.WARNING, maybeFormat(message, args), null); | |
} | |
@Override public final void w(Throwable t, String message, Object... args) { | |
throwShade(MessageLevel.WARNING, maybeFormat(message, args), t); | |
} | |
@Override public final void e(String message, Object... args) { | |
throwShade(MessageLevel.ERROR, maybeFormat(message, args), null); | |
} | |
@Override public final void e(Throwable t, String message, Object... args) { | |
throwShade(MessageLevel.ERROR, maybeFormat(message, args), t); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment