blob: 255eb072832df7ab53527100eac26e317dd5725e [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
Thiemo Nagel2cdda48e2018-11-15 17:49:2165### Rule #1: Never log user data or PII (Personal Identification Information)
dgn97b9a252015-09-02 16:36:4866
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
Thiemo Nagel2cdda48e2018-11-15 17:49:2173If you really need to print something, print a series of Xs instead
74(e.g. "XXXXXX"), or print a truncated hash of the data instead. Truncation is
dgn97b9a252015-09-02 16:36:4875required to make it harder for an attacker to recover the full data through
76rainbow tables and similar methods.
77
Thiemo Nagel2cdda48e2018-11-15 17:49:2178Similarly, avoid dumping API keys, cookies, IP addresses, URLs, page content,
79etc...
dgn97b9a252015-09-02 16:36:4880
Thiemo Nagel2cdda48e2018-11-15 17:49:2181### Rule #2: Do not build debug logs in production code
dgn97b9a252015-09-02 16:36:4882
83The log methods are removed in release builds using Proguard. Because log
84messages might not be written, the cost of creating them should also be avoided.
85This can be done using three complementary ways:
86
87#### Use string formatting instead of concatenations
88
89```java
90// BAD
91Log.d(TAG, "I " + preference + " writing logs.");
92
93// BETTER
94Log.d(TAG, "I %s writing logs.", preference);
95```
96
97Proguard removes the method call itself, but doesn't do anything about the
98arguments. The method's arguments will still be computed and provided as
99input. The first call above will always lead to the creation of a
100`StringBuilder` and a few concatenations, while the second just passes the
101arguments and won't need that.
102
103#### Guard expensive calls
104
105Sometimes the values to log aren't readily available and need to be computed
106specially. This should be avoided when logging is disabled.
107
108```java
109static private final boolean DEBUG = false; // debug toggle.
110...
111if (DEBUG) {
112 Log.i(TAG, createThatExpensiveLogMessage(activity))
113}
114```
115
116Because the variable is a `static final` that can be evaluated at compile
117time, the Java compiler will optimize out all guarded calls from the
118generated `.class` file. Changing it however requires editing each of the
119files for which debug should be enabled and recompiling.
120
121#### Annotate debug functions with the `@RemovableInRelease` annotation.
122
123That annotation tells Proguard to assume that a given function has no side
124effects, and is called only for its returned value. If this value is unused,
125the call will be removed. If the function is not called at all, it will also
126be removed. Since Proguard is already used to strip debug and verbose calls
127out of release builds, this annotation allows it to have a deeper action by
128removing also function calls used to generate the log call's arguments.
129
130```java
131/* If that function is only used in Log.d calls, proguard should
132 * completely remove it from the release builds. */
133@RemovableInRelease
134private static String getSomeDebugLogString(Thing[] things) {
135 StringBuilder sb = new StringBuilder(
136 "Reporting " + thing.length + " things: ");
137 for (Thing thing : things) {
138 sb.append('\n').append(thing.id).append(' ').append(report.foo);
139 }
140 return sb.toString();
141}
142
143public void bar() {
144 ...
145 Log.d(TAG, getSomeDebugLogString(things)); /* The line is removed in
146 * release builds. */
147}
148```
149
150Again, this is useful only if the input to that function are variables
151already available in the scope. The idea is to move computations,
152concatenations, etc. to a place where that can be removed when not needed,
153without invading the main function's logic. It can then have a similar
154effect as guarding with a static final property that would be enabled in
155Debug and disabled in Release.
156
157### Rule #3: Favor small log messages
158
159This is still related to the global fixed-sized kernel buffer used to keep all
160logs. Try to make your log information as terse as possible. This reduces the
161risk of pushing interesting log data out of the buffer when something really
162nasty happens. It's really better to have a single-line log message, than
163several ones. I.e. don't use:
164
165```java
166Log.GROUP.d(TAG, "field1 = %s", value1);
167Log.GROUP.d(TAG, "field2 = %s", value2);
168Log.GROUP.d(TAG, "field3 = %s", value3);
169```
170
171Instead, write this as:
172
173```java
174Log.d(TAG, "field1 = %s, field2 = %s, field3 = %s", value1, value2, value3);
175```
176
177That doesn't seem to be much different if you count overall character counts,
178but each independent log entry also implies a small, but non-trivial header, in
179the kernel log buffer. And since every byte count, you can also try something
180even shorter, as in:
181
182```java
183Log.d(TAG, "fields [%s,%s,%s]", value1, value2, value3);
184```
185
186## Filtering logs
187
188Logcat allows filtering by specifying tags and the associated level:
189
190```shell
191adb logcat [TAG_EXPR:LEVEL]...
dgn38736db2015-09-18 19:20:51192adb logcat cr_YourModuleTag:D *:S
dgn97b9a252015-09-02 16:36:48193```
194
195This shows only logs having a level higher or equal to DEBUG for
dgn38736db2015-09-18 19:20:51196`cr_YourModuleTag`, and SILENT (nothing is logged at this level or higher, so it
dgn97b9a252015-09-02 16:36:48197silences the tags) for everything else. You can persist a filter by setting an
198environment variable:
199
200```shell
dgn38736db2015-09-18 19:20:51201export ANDROID_LOG_TAGS="cr_YourModuleTag:D *:S"
dgn97b9a252015-09-02 16:36:48202```
203
Nicolas Dossou-gbete5700c6e2015-12-21 18:53:09204The syntax does not support tag expansion or regular expressions other than `*`
205for all tags. Please use `grep` or a similar tool to refine your filters
206further.
207
dgn97b9a252015-09-02 16:36:48208For more, see the [related page on developer.android.com]
xiaoyin.l1003c0b2016-12-06 02:51:17209(https://developer.android.com/tools/debugging/debugging-log.html#filteringOutput)
dgn0325c0f2016-01-21 13:38:57210
211## Logs in JUnit tests
212
213We use [robolectric](http://robolectric.org/) to run our JUnit tests. It
214replaces some of the Android framework classes with "Shadow" classes
215to ensure that we can run our code in a regular JVM. `android.util.Log` is one
216of those replaced classes, and by default calling `Log` methods doesn't print
217anything.
218
219That default is not changed in the normal configuration, but if you need to
220enable logging locally or for a specific test, just add those few lines to your
221test:
222
223```java
224@Before
225public void setUp() {
226 ShadowLog.stream = System.out;
227 //you other setup here
228}
229```