blob: 6c37664df4ce3e20de4310653bea24f6ada49a4f [file] [log] [blame] [view]
dgn97b9a252015-09-02 16:36:481# Logging #
2
3[TOC]
4
5
6## Overview
7
8Logging used to be done using Android's [android.util.Log]
xiaoyin.l1003c0b2016-12-06 02:51:179(https://developer.android.com/reference/android/util/Log.html).
dgn97b9a252015-09-02 16:36:4810
11A wrapper on that is now available: org.chromium.base.Log. It is designed to
12write logs as belonging to logical groups going beyond single classes, and to
13make it easy to switch logging on or off for individual groups.
14
15Usage:
16
17```java
dgn38736db2015-09-18 19:20:5118private static final String TAG = "YourModuleTag";
dgn97b9a252015-09-02 16:36:4819...
20Log.i(TAG, "Logged INFO message.");
21Log.d(TAG, "Some DEBUG info: %s", data);
22```
23
24Output:
25
26```
dgn38736db2015-09-18 19:20:5127I/cr_YourModuleTag: ( 999): Logged INFO message
28D/cr_YourModuleTag: ( 999): [MyClass.java:42] Some DEBUG info: data.toString
dgn97b9a252015-09-02 16:36:4829```
30
31Here, **TAG** will be a feature or package name, "MediaRemote" or "NFC" for
dgn38736db2015-09-18 19:20:5132example. In most cases, the class name is not needed. It will be prepended by
33the "cr_" prefix to make obvious which logs are coming from Chrome.
dgn97b9a252015-09-02 16:36:4834
35### Verbose and Debug logs have special handling ###
36
37* `Log.v` and `Log.d` Calls made using `org.chromium.base.Log` are stripped
38 out of production binaries using Proguard. There is no way to get those logs
39 in release builds.
40
41* The file name and line number will be prepended to the log message.
42 For higher priority logs, those are not added for performance concerns.
43
44### An exception trace is printed when the exception is the last parameter ###
45
46As with `java.util.Log`, putting a throwable as last parameter will dump the
47corresponding stack trace:
48
49```java
50Log.i(TAG, "An error happened: %s", e)
51```
52
53```
dgn38736db2015-09-18 19:20:5154I/cr_YourModuleTag: ( 999): An error happened: This is the exception's message
55I/cr_YourModuleTag: ( 999): java.lang.Exception: This is the exception's message
56I/cr_YourModuleTag: ( 999): at foo.bar.MyClass.test(MyClass.java:42)
57I/cr_YourModuleTag: ( 999): ...
dgn97b9a252015-09-02 16:36:4858```
59
60Having the exception as last parameter doesn't prevent it from being used for
61string formatting.
62
63## Logging Best Practices
64
65### Rule #1: Never log PII (Personal Identification Information):
66
67This is a huge concern, because other applications can access the log and
68extract a lot of data from your own by doing so. Even if JellyBean restricted
69this, people are going to run your application on rooted devices and allow some
70apps to access it. Also anyone with USB access to the device can use ADB to get
71the full logcat and get the same data right now.
72
73If you really need to print something , print a series of Xs instead
74(e.g. "XXXXXX"), or print a truncated hash of the PII instead. Truncation is
75required to make it harder for an attacker to recover the full data through
76rainbow tables and similar methods.
77
78Similarly, avoid dumping API keys, cookies, etc...
79
80### Rule #2: Do not build debug logs in production code:
81
82The log methods are removed in release builds using Proguard. Because log
83messages might not be written, the cost of creating them should also be avoided.
84This can be done using three complementary ways:
85
86#### Use string formatting instead of concatenations
87
88```java
89// BAD
90Log.d(TAG, "I " + preference + " writing logs.");
91
92// BETTER
93Log.d(TAG, "I %s writing logs.", preference);
94```
95
96Proguard removes the method call itself, but doesn't do anything about the
97arguments. The method's arguments will still be computed and provided as
98input. The first call above will always lead to the creation of a
99`StringBuilder` and a few concatenations, while the second just passes the
100arguments and won't need that.
101
102#### Guard expensive calls
103
104Sometimes the values to log aren't readily available and need to be computed
105specially. This should be avoided when logging is disabled.
106
107```java
108static private final boolean DEBUG = false; // debug toggle.
109...
110if (DEBUG) {
111 Log.i(TAG, createThatExpensiveLogMessage(activity))
112}
113```
114
115Because the variable is a `static final` that can be evaluated at compile
116time, the Java compiler will optimize out all guarded calls from the
117generated `.class` file. Changing it however requires editing each of the
118files for which debug should be enabled and recompiling.
119
120#### Annotate debug functions with the `@RemovableInRelease` annotation.
121
122That annotation tells Proguard to assume that a given function has no side
123effects, and is called only for its returned value. If this value is unused,
124the call will be removed. If the function is not called at all, it will also
125be removed. Since Proguard is already used to strip debug and verbose calls
126out of release builds, this annotation allows it to have a deeper action by
127removing also function calls used to generate the log call's arguments.
128
129```java
130/* If that function is only used in Log.d calls, proguard should
131 * completely remove it from the release builds. */
132@RemovableInRelease
133private static String getSomeDebugLogString(Thing[] things) {
134 StringBuilder sb = new StringBuilder(
135 "Reporting " + thing.length + " things: ");
136 for (Thing thing : things) {
137 sb.append('\n').append(thing.id).append(' ').append(report.foo);
138 }
139 return sb.toString();
140}
141
142public void bar() {
143 ...
144 Log.d(TAG, getSomeDebugLogString(things)); /* The line is removed in
145 * release builds. */
146}
147```
148
149Again, this is useful only if the input to that function are variables
150already available in the scope. The idea is to move computations,
151concatenations, etc. to a place where that can be removed when not needed,
152without invading the main function's logic. It can then have a similar
153effect as guarding with a static final property that would be enabled in
154Debug and disabled in Release.
155
156### Rule #3: Favor small log messages
157
158This is still related to the global fixed-sized kernel buffer used to keep all
159logs. Try to make your log information as terse as possible. This reduces the
160risk of pushing interesting log data out of the buffer when something really
161nasty happens. It's really better to have a single-line log message, than
162several ones. I.e. don't use:
163
164```java
165Log.GROUP.d(TAG, "field1 = %s", value1);
166Log.GROUP.d(TAG, "field2 = %s", value2);
167Log.GROUP.d(TAG, "field3 = %s", value3);
168```
169
170Instead, write this as:
171
172```java
173Log.d(TAG, "field1 = %s, field2 = %s, field3 = %s", value1, value2, value3);
174```
175
176That doesn't seem to be much different if you count overall character counts,
177but each independent log entry also implies a small, but non-trivial header, in
178the kernel log buffer. And since every byte count, you can also try something
179even shorter, as in:
180
181```java
182Log.d(TAG, "fields [%s,%s,%s]", value1, value2, value3);
183```
184
185## Filtering logs
186
187Logcat allows filtering by specifying tags and the associated level:
188
189```shell
190adb logcat [TAG_EXPR:LEVEL]...
dgn38736db2015-09-18 19:20:51191adb logcat cr_YourModuleTag:D *:S
dgn97b9a252015-09-02 16:36:48192```
193
194This shows only logs having a level higher or equal to DEBUG for
dgn38736db2015-09-18 19:20:51195`cr_YourModuleTag`, and SILENT (nothing is logged at this level or higher, so it
dgn97b9a252015-09-02 16:36:48196silences the tags) for everything else. You can persist a filter by setting an
197environment variable:
198
199```shell
dgn38736db2015-09-18 19:20:51200export ANDROID_LOG_TAGS="cr_YourModuleTag:D *:S"
dgn97b9a252015-09-02 16:36:48201```
202
Nicolas Dossou-gbete5700c6e2015-12-21 18:53:09203The syntax does not support tag expansion or regular expressions other than `*`
204for all tags. Please use `grep` or a similar tool to refine your filters
205further.
206
dgn97b9a252015-09-02 16:36:48207For more, see the [related page on developer.android.com]
xiaoyin.l1003c0b2016-12-06 02:51:17208(https://developer.android.com/tools/debugging/debugging-log.html#filteringOutput)
dgn0325c0f2016-01-21 13:38:57209
210## Logs in JUnit tests
211
212We use [robolectric](http://robolectric.org/) to run our JUnit tests. It
213replaces some of the Android framework classes with "Shadow" classes
214to ensure that we can run our code in a regular JVM. `android.util.Log` is one
215of those replaced classes, and by default calling `Log` methods doesn't print
216anything.
217
218That default is not changed in the normal configuration, but if you need to
219enable logging locally or for a specific test, just add those few lines to your
220test:
221
222```java
223@Before
224public void setUp() {
225 ShadowLog.stream = System.out;
226 //you other setup here
227}
228```