diff --git a/timber-sample/src/main/java/com/example/timber/ExampleApp.java b/timber-sample/src/main/java/com/example/timber/ExampleApp.java index 15e2c8ec3..e7d8cb48e 100644 --- a/timber-sample/src/main/java/com/example/timber/ExampleApp.java +++ b/timber-sample/src/main/java/com/example/timber/ExampleApp.java @@ -3,6 +3,9 @@ import android.app.Application; import android.support.annotation.NonNull; import android.util.Log; + +import java.util.Map; + import timber.log.Timber; import static timber.log.Timber.DebugTree; @@ -15,6 +18,7 @@ public class ExampleApp extends Application { Timber.plant(new DebugTree()); } else { Timber.plant(new CrashReportingTree()); + Timber.plant(new StructuredLoggingTree()); } } @@ -36,4 +40,20 @@ private static class CrashReportingTree extends Timber.Tree { } } } + + /** A tree which logs important events in a structured format. */ + private static class StructuredLoggingTree extends Timber.Tree { + @Override protected void log(int priority, String tag, @NonNull String message, Throwable t) { + log(priority, tag, message, t, null); + } + + @Override protected void log(int priority, String tag, @NonNull String message, Throwable t, + Map metadata) { + if (priority == Log.VERBOSE || priority == Log.DEBUG) { + return; + } + + FakeCrashLibrary.log(priority, message, metadata); + } + } } diff --git a/timber-sample/src/main/java/com/example/timber/FakeCrashLibrary.java b/timber-sample/src/main/java/com/example/timber/FakeCrashLibrary.java index b77c77d4f..d47f693b6 100644 --- a/timber-sample/src/main/java/com/example/timber/FakeCrashLibrary.java +++ b/timber-sample/src/main/java/com/example/timber/FakeCrashLibrary.java @@ -1,11 +1,17 @@ package com.example.timber; +import java.util.Map; + /** Not a real crash reporting library! */ public final class FakeCrashLibrary { public static void log(int priority, String tag, String message) { // TODO add log entry to circular buffer. } + public static void log(int priority, String message, Map metadata) { + // TODO add log entry with metadata + } + public static void logWarning(Throwable t) { // TODO report non-fatal warning. } diff --git a/timber-sample/src/main/java/com/example/timber/ui/DemoActivity.java b/timber-sample/src/main/java/com/example/timber/ui/DemoActivity.java index 34df2aeec..9aae5a811 100644 --- a/timber-sample/src/main/java/com/example/timber/ui/DemoActivity.java +++ b/timber-sample/src/main/java/com/example/timber/ui/DemoActivity.java @@ -5,6 +5,9 @@ import android.widget.Button; import android.widget.Toast; +import java.util.HashMap; +import java.util.Map; + import butterknife.ButterKnife; import butterknife.OnClick; import com.example.timber.R; @@ -24,6 +27,10 @@ public class DemoActivity extends Activity { @OnClick({ R.id.hello, R.id.hey, R.id.hi }) public void greetingClicked(Button button) { Timber.i("A button with ID %s was clicked to say '%s'.", button.getId(), button.getText()); + Map eventMetadata = new HashMap<>(); + eventMetadata.put("event-type", "button-pressed"); + eventMetadata.put("button-id", String.valueOf(button.getId())); + Timber.v(eventMetadata, "This is an example of a structured log"); Toast.makeText(this, "Check logcat for a greeting!", LENGTH_SHORT).show(); } } diff --git a/timber/src/main/java/timber/log/Timber.java b/timber/src/main/java/timber/log/Timber.java index a927276b5..78445d849 100644 --- a/timber/src/main/java/timber/log/Timber.java +++ b/timber/src/main/java/timber/log/Timber.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NonNls; @@ -23,91 +24,186 @@ public static void v(@NonNls String message, Object... args) { TREE_OF_SOULS.v(message, args); } + /** Log a verbose message with metadata and optional format args. */ + public static void v(Map metadata, @NonNls String message, Object... args) { + TREE_OF_SOULS.v(metadata, message, args); + } + /** Log a verbose exception and a message with optional format args. */ public static void v(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.v(t, message, args); } + /** Log a verbose exception and a message with optional format args. */ + public static void v(Throwable t, Map metadata, + @NonNls String message, Object... args) { + TREE_OF_SOULS.v(t, metadata, message, args); + } + /** Log a verbose exception. */ public static void v(Throwable t) { TREE_OF_SOULS.v(t); } + /** Log a verbose exception with metadata. */ + public static void v(Map metadata, Throwable t) { + TREE_OF_SOULS.v(metadata, t); + } + /** Log a debug message with optional format args. */ public static void d(@NonNls String message, Object... args) { TREE_OF_SOULS.d(message, args); } + /** Log a debug message with metadata and optional format args. */ + public static void d(Map metadata, @NonNls String message, Object... args) { + TREE_OF_SOULS.d(metadata, message, args); + } /** Log a debug exception and a message with optional format args. */ public static void d(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.d(t, message, args); } + /** Log a debug exception and a message with optional format args. */ + public static void d(Throwable t, Map metadata, + @NonNls String message, Object... args) { + TREE_OF_SOULS.d(t, metadata, message, args); + } + /** Log a debug exception. */ public static void d(Throwable t) { TREE_OF_SOULS.d(t); } + /** Log a debug exception with metadata. */ + public static void d(Map metadata, Throwable t) { + TREE_OF_SOULS.d(metadata, t); + } + /** Log an info message with optional format args. */ public static void i(@NonNls String message, Object... args) { TREE_OF_SOULS.i(message, args); } + /** Log an info message with metadata and optional format args. */ + public static void i(Map metaData, @NonNls String message, Object... args) { + TREE_OF_SOULS.i(metaData, message, args); + } + /** Log an info exception and a message with optional format args. */ public static void i(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.i(t, message, args); } + /** Log an info exception and a message with optional format args. */ + public static void i(Throwable t, Map metadata, + @NonNls String message, Object... args) { + TREE_OF_SOULS.i(t, metadata, message, args); + } + /** Log an info exception. */ public static void i(Throwable t) { TREE_OF_SOULS.i(t); } + /** Log an info exception with metadata. */ + public static void i(Map metadata, Throwable t) { + TREE_OF_SOULS.i(metadata, t); + } + /** Log a warning message with optional format args. */ public static void w(@NonNls String message, Object... args) { TREE_OF_SOULS.w(message, args); } + /** Log a warning message with metadata and optional format args. */ + public static void w(Map metaData, @NonNls String message, Object... args) { + TREE_OF_SOULS.w(metaData, message, args); + } + /** Log a warning exception and a message with optional format args. */ public static void w(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.w(t, message, args); } + /** Log a verbose exception and a message with optional format args. */ + public static void w(Throwable t, Map metadata, + @NonNls String message, Object... args) { + TREE_OF_SOULS.w(t, metadata, message, args); + } + /** Log a warning exception. */ public static void w(Throwable t) { TREE_OF_SOULS.w(t); } + /** Log a warning exception with metadata. */ + public static void w(Map metadata, Throwable t) { + TREE_OF_SOULS.w(metadata, t); + } + /** Log an error message with optional format args. */ public static void e(@NonNls String message, Object... args) { TREE_OF_SOULS.e(message, args); } + /** Log an error message with metadata and optional format args. */ + public static void e(Map metaData, @NonNls String message, Object... args) { + TREE_OF_SOULS.e(metaData, message, args); + } + /** Log an error exception and a message with optional format args. */ public static void e(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.e(t, message, args); } + /** Log an error exception and a message with optional format args. */ + public static void e(Throwable t, Map metadata, @NonNls String message, + Object... args) { + TREE_OF_SOULS.e(t, metadata, message, args); + } + /** Log an error exception. */ public static void e(Throwable t) { TREE_OF_SOULS.e(t); } + /** Log an error exception with metadata. */ + public static void e(Map metadata, Throwable t) { + TREE_OF_SOULS.e(metadata, t); + } + /** Log an assert message with optional format args. */ public static void wtf(@NonNls String message, Object... args) { TREE_OF_SOULS.wtf(message, args); } + /** Log an assert message with metadata and optional format args. */ + public static void wtf(Map metaData, @NonNls String message, Object... args) { + TREE_OF_SOULS.wtf(metaData, message, args); + } + /** Log an assert exception and a message with optional format args. */ public static void wtf(Throwable t, @NonNls String message, Object... args) { TREE_OF_SOULS.wtf(t, message, args); } + /** Log an assert exception and a message with optional format args. */ + public static void wtf(Throwable t, Map metadata, @NonNls String message, + Object... args) { + TREE_OF_SOULS.wtf(t, metadata, message, args); + } + /** Log an assert exception. */ public static void wtf(Throwable t) { TREE_OF_SOULS.wtf(t); } + /** Log an assert exception with metadata. */ + public static void wtf(Map metadata, Throwable t) { + TREE_OF_SOULS.wtf(metadata, t); + } + /** Log at {@code priority} a message with optional format args. */ public static void log(int priority, @NonNls String message, Object... args) { TREE_OF_SOULS.log(priority, message, args); @@ -225,6 +321,14 @@ public static int treeCount() { } } + @Override public void v(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].v(metadata, message, args); + } + } + @Override public void v(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -233,6 +337,15 @@ public static int treeCount() { } } + @Override public void v(Throwable t, Map metadata, + String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].v(t, metadata, message, args); + } + } + @Override public void v(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -241,6 +354,14 @@ public static int treeCount() { } } + @Override public void v(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].v(metadata, t); + } + } + @Override public void d(String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -249,6 +370,14 @@ public static int treeCount() { } } + @Override public void d(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].d(metadata, message, args); + } + } + @Override public void d(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -257,6 +386,15 @@ public static int treeCount() { } } + @Override public void d(Throwable t, Map metadata, + String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].d(t, metadata, message, args); + } + } + @Override public void d(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -265,6 +403,14 @@ public static int treeCount() { } } + @Override public void d(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].d(metadata, t); + } + } + @Override public void i(String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -273,6 +419,14 @@ public static int treeCount() { } } + @Override public void i(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].i(metadata, message, args); + } + } + @Override public void i(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -281,6 +435,15 @@ public static int treeCount() { } } + @Override public void i(Throwable t, Map metadata, + String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].i(t, metadata, message, args); + } + } + @Override public void i(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -289,6 +452,14 @@ public static int treeCount() { } } + @Override public void i(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].i(metadata, t); + } + } + @Override public void w(String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -297,6 +468,14 @@ public static int treeCount() { } } + @Override public void w(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].w(metadata, message, args); + } + } + @Override public void w(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -305,6 +484,15 @@ public static int treeCount() { } } + @Override public void w(Throwable t, Map metadata, String message, + Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].w(t, metadata, message, args); + } + } + @Override public void w(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -313,6 +501,14 @@ public static int treeCount() { } } + @Override public void w(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].w(metadata, t); + } + } + @Override public void e(String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -321,6 +517,14 @@ public static int treeCount() { } } + @Override public void e(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].e(metadata, message, args); + } + } + @Override public void e(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -329,6 +533,15 @@ public static int treeCount() { } } + @Override public void e(Throwable t, Map metadata, String message, + Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].e(t, metadata, message, args); + } + } + @Override public void e(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -337,6 +550,14 @@ public static int treeCount() { } } + @Override public void e(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].e(metadata, t); + } + } + @Override public void wtf(String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -345,6 +566,14 @@ public static int treeCount() { } } + @Override public void wtf(Map metadata, String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].wtf(metadata, message, args); + } + } + @Override public void wtf(Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -353,6 +582,15 @@ public static int treeCount() { } } + @Override public void wtf(Throwable t, Map metadata, String message, + Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].wtf(t, metadata, message, args); + } + } + @Override public void wtf(Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -361,6 +599,14 @@ public static int treeCount() { } } + @Override public void wtf(Map metadata, Throwable t) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].wtf(metadata, t); + } + } + @Override public void log(int priority, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -369,6 +615,15 @@ public static int treeCount() { } } + @Override public void log(int priority, Map metadata, + String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].log(priority, metadata, message, args); + } + } + @Override public void log(int priority, Throwable t, String message, Object... args) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -377,6 +632,15 @@ public static int treeCount() { } } + @Override public void log(int priority, Throwable t, Map metadata, + String message, Object... args) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].log(priority, t, metadata, message, args); + } + } + @Override public void log(int priority, Throwable t) { Tree[] forest = forestAsArray; //noinspection ForLoopReplaceableByForEach @@ -385,6 +649,19 @@ public static int treeCount() { } } + @Override public void log(int priority, Throwable t, Map metadata) { + Tree[] forest = forestAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0, count = forest.length; i < count; i++) { + forest[i].log(priority, t, metadata); + } + } + + @Override protected void log(int priority, String tag, @NotNull String message, Throwable t, + Map metadata) { + throw new AssertionError("Missing override for log method. test test"); + } + @Override protected void log(int priority, String tag, @NotNull String message, Throwable t) { throw new AssertionError("Missing override for log method."); } @@ -409,107 +686,213 @@ String getTag() { /** Log a verbose message with optional format args. */ public void v(String message, Object... args) { - prepareLog(Log.VERBOSE, null, message, args); + prepareLog(Log.VERBOSE, null, null, message, args); + } + + /** Log a verbose message with metadata and optional format args. */ + public void v(Map metadata, String message, Object... args) { + prepareLog(Log.VERBOSE, null, metadata, message, args); } /** Log a verbose exception and a message with optional format args. */ public void v(Throwable t, String message, Object... args) { - prepareLog(Log.VERBOSE, t, message, args); + prepareLog(Log.VERBOSE, t, null, message, args); + } + + /** Log a verbose exception with metadata and a message with optional format args. */ + public void v(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.VERBOSE, t, metadata, message, args); } /** Log a verbose exception. */ public void v(Throwable t) { - prepareLog(Log.VERBOSE, t, null); + prepareLog(Log.VERBOSE, t, null, null); + } + + /** Log a verbose exception with metadata. */ + public void v(Map metadata, Throwable t) { + prepareLog(Log.VERBOSE, t, metadata, null); } /** Log a debug message with optional format args. */ public void d(String message, Object... args) { - prepareLog(Log.DEBUG, null, message, args); + prepareLog(Log.DEBUG, null, null, message, args); + } + + /** Log a debug message with metadata and optional format args. */ + public void d(Map metadata, String message, Object... args) { + prepareLog(Log.DEBUG, null, metadata, message, args); } /** Log a debug exception and a message with optional format args. */ public void d(Throwable t, String message, Object... args) { - prepareLog(Log.DEBUG, t, message, args); + prepareLog(Log.DEBUG, t, null, message, args); + } + + /** Log a debug exception with metadata and a message with optional format args. */ + public void d(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.DEBUG, t, metadata, message, args); } /** Log a debug exception. */ public void d(Throwable t) { - prepareLog(Log.DEBUG, t, null); + prepareLog(Log.DEBUG, t, null, null); + } + + /** Log a verbose exception with metadata. */ + public void d(Map metadata, Throwable t) { + prepareLog(Log.DEBUG, t, metadata, null); } /** Log an info message with optional format args. */ public void i(String message, Object... args) { - prepareLog(Log.INFO, null, message, args); + prepareLog(Log.INFO, null, null, message, args); + } + + /** Log an info message with metadata and optional format args. */ + public void i(Map metadata, String message, Object... args) { + prepareLog(Log.INFO, null, metadata, message, args); } /** Log an info exception and a message with optional format args. */ public void i(Throwable t, String message, Object... args) { - prepareLog(Log.INFO, t, message, args); + prepareLog(Log.INFO, t, null, message, args); + } + + /** Log an info exception with metadata and a message with optional format args. */ + public void i(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.INFO, t, metadata, message, args); } /** Log an info exception. */ public void i(Throwable t) { - prepareLog(Log.INFO, t, null); + prepareLog(Log.INFO, t, null, null); + } + + /** Log an info exception with metadata. */ + public void i(Map metadata, Throwable t) { + prepareLog(Log.INFO, t, metadata, null); } /** Log a warning message with optional format args. */ public void w(String message, Object... args) { - prepareLog(Log.WARN, null, message, args); + prepareLog(Log.WARN, null, null, message, args); + } + + /** Log a warning message with metadata and optional format args. */ + public void w(Map metadata, String message, Object... args) { + prepareLog(Log.WARN, null, metadata, message, args); } /** Log a warning exception and a message with optional format args. */ public void w(Throwable t, String message, Object... args) { - prepareLog(Log.WARN, t, message, args); + prepareLog(Log.WARN, t, null, message, args); + } + + /** Log a warning exception with metadata and a message with optional format args. */ + public void w(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.WARN, t, metadata, message, args); } /** Log a warning exception. */ public void w(Throwable t) { - prepareLog(Log.WARN, t, null); + prepareLog(Log.WARN, t, null, null); + } + + /** Log a warning exception with metadata. */ + public void w(Map metadata, Throwable t) { + prepareLog(Log.WARN, t, metadata, null); } /** Log an error message with optional format args. */ public void e(String message, Object... args) { - prepareLog(Log.ERROR, null, message, args); + prepareLog(Log.ERROR, null, null, message, args); + } + + /** Log an error message with metadata and optional format args. */ + public void e(Map metadata, String message, Object... args) { + prepareLog(Log.ERROR, null, metadata, message, args); } /** Log an error exception and a message with optional format args. */ public void e(Throwable t, String message, Object... args) { - prepareLog(Log.ERROR, t, message, args); + prepareLog(Log.ERROR, t, null, message, args); + } + + /** Log an error exception with metadata and a message with optional format args. */ + public void e(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.ERROR, t, metadata, message, args); } /** Log an error exception. */ public void e(Throwable t) { - prepareLog(Log.ERROR, t, null); + prepareLog(Log.ERROR, t, null, null); + } + + /** Log an error exception with metadata. */ + public void e(Map metadata, Throwable t) { + prepareLog(Log.ERROR, t, metadata, null); } /** Log an assert message with optional format args. */ public void wtf(String message, Object... args) { - prepareLog(Log.ASSERT, null, message, args); + prepareLog(Log.ASSERT, null, null, message, args); + } + + /** Log a verbose message with metadata and optional format args. */ + public void wtf(Map metadata, String message, Object... args) { + prepareLog(Log.VERBOSE, null, metadata, message, args); } /** Log an assert exception and a message with optional format args. */ public void wtf(Throwable t, String message, Object... args) { - prepareLog(Log.ASSERT, t, message, args); + prepareLog(Log.ASSERT, t, null, message, args); + } + + /** Log a verbose exception with metadata and a message with optional format args. */ + public void wtf(Throwable t, Map metadata, String message, Object... args) { + prepareLog(Log.VERBOSE, t, metadata, message, args); } /** Log an assert exception. */ public void wtf(Throwable t) { - prepareLog(Log.ASSERT, t, null); + prepareLog(Log.ASSERT, t, null, null); + } + + /** Log a verbose exception with metadata. */ + public void wtf(Map metadata, Throwable t) { + prepareLog(Log.VERBOSE, t, metadata, null); } /** Log at {@code priority} a message with optional format args. */ public void log(int priority, String message, Object... args) { - prepareLog(priority, null, message, args); + prepareLog(priority, null, null, message, args); + } + + /** Log at {@code priority} metadata and a message with optional format args. */ + public void log(int priority, Map metadata, String message, Object... args) { + prepareLog(priority, null, metadata, message, args); } /** Log at {@code priority} an exception and a message with optional format args. */ public void log(int priority, Throwable t, String message, Object... args) { - prepareLog(priority, t, message, args); + prepareLog(priority, t, null, message, args); + } + + /** Log at {@code priority} an exception, metadata and a message with optional format args. */ + public void log(int priority, Throwable t, Map metadata, + String message, Object... args) { + prepareLog(priority, t, metadata, message, args); } /** Log at {@code priority} an exception. */ public void log(int priority, Throwable t) { - prepareLog(priority, t, null); + prepareLog(priority, t, null, null); + } + + /** Log at {@code priority} an exception and metadata. */ + public void log(int priority, Throwable t, Map metadata) { + prepareLog(priority, t, metadata, null); } /** @@ -527,7 +910,8 @@ protected boolean isLoggable(@Nullable String tag, int priority) { return isLoggable(priority); } - private void prepareLog(int priority, Throwable t, String message, Object... args) { + private void prepareLog(int priority, Throwable t, Map metadata, + String message, Object... args) { // Consume tag even when message is not loggable so that next message is correctly tagged. String tag = getTag(); @@ -551,7 +935,11 @@ private void prepareLog(int priority, Throwable t, String message, Object... arg } } - log(priority, tag, message, t); + if (metadata == null) { + log(priority, tag, message, t); + } else { + log(priority, tag, message, t, metadata); + } } /** @@ -571,6 +959,20 @@ private String getStackTraceString(Throwable t) { return sw.toString(); } + /** + * Write a log message to its destination. This must be overridden in order to capture metadata. + * + * @param priority Log level. See {@link Log} for constants. + * @param tag Explicit or inferred tag. May be {@code null}. + * @param message Formatted log message. May be {@code null}, but then {@code t} will not be. + * @param t Accompanying exceptions. May be {@code null}, but then {@code message} will not be. + * @param metadata metadata associated with the log statement. May be {@code null}. + */ + protected void log(int priority, @Nullable String tag, @NotNull String message, + @Nullable Throwable t, @Nullable Map metadata) { + throw new IllegalArgumentException("You must override this method to log with metadata"); + } + /** * Write a log message to its destination. Called for all level-specific methods by default. * diff --git a/timber/src/test/java/timber/log/TimberTest.java b/timber/src/test/java/timber/log/TimberTest.java index df509f281..a3aa3badd 100644 --- a/timber/src/test/java/timber/log/TimberTest.java +++ b/timber/src/test/java/timber/log/TimberTest.java @@ -6,7 +6,9 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import org.jetbrains.annotations.NotNull; import org.junit.After; @@ -41,7 +43,7 @@ public class TimberTest { Timber.d("Test"); assertLog() - .hasDebugMessage("TimberTest:41", "Test") + .hasDebugMessage("TimberTest:43", "Test") .hasNoMoreMessages(); } @@ -510,6 +512,31 @@ protected String formatMessage(@NotNull String message, @NotNull Object[] args) .hasNoMoreMessages(); } + @Test public void loggingMetadataFailsWhenNotImplemented() { + Timber.plant(new Timber.DebugTree()); + try { + Timber.d(new HashMap(), "TimberTest"); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageThat().startsWith("You must override this method to log with metadata"); + } + } + + @Test public void loggingMetadataWorksWhenImplemented() { + final String key = "test-key"; + final String value = "test-content"; + Timber.Tree testTree = new Timber.DebugTree() { + @Override protected void log(int priority, String tag, @NotNull String message, Throwable t, + Map metadata) { + Object result = metadata.get(key); + assertThat(result).isEqualTo(value); + } + }; + Timber.plant(testTree); + Map metadata = new HashMap<>(); + metadata.put(key, value); + Timber.d(metadata, "TimberTest"); + } + private static T truncatedThrowable(Class throwableClass) { try { T throwable = throwableClass.newInstance();