Android的Log机制分析

概述

LOG是广泛使用的用来记录程序执行过程的机制,它既可以用于程序调试,也可以用于产品运营中的事件记录。在Android系统中,提供了简单、便利的LOG机制,开发人员可以方便地使用。

Log的使用

本节介绍Kernel中内核态的printk和用户态的Log使用

内核态Kernel log的使用

printk的使用

Android是基于Linux内核的,所以Linux中的printk同样适用于Android.printk提供了8种日志级别
kernel/include/linux/kern_levels.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef __KERN_LEVELS_H__
#define __KERN_LEVELS_H__

#define KERN_SOH "\001" /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\001'

#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */

#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */

/*
* Annotation for a "continued" line of log printout (only done after a
* line that had no enclosing \n). Only to be used by core/arch code
* during early bootup (a continued line is not SMP-safe otherwise).
*/
#define KERN_CONT ""

#endif

printk在代码中的使用方法

1
printk(KERN_ALERT"This is the log printed by printk in linux kernel space.");

或者

1
printk("1" "This is the log printed by printk in linux kernel space.");

KERN_ALERT表示日志级别,后面紧跟着要格式化字符串。

printk的log查看方法
在Android系统中,printk输出的日志信息保存在/proc/kmsg中,要查看/proc/kmsg的内容,使用命令
cat /proc/kmsg
cat /dev/kmsg
adb logcat -b kernel

查看当前日志打印级别

1
cat /proc/sys/kernel/printk

输出结果:

1
0 6 1 7

四个值的含义:控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别

★Tips: 如何调整Kernel log的日志级别?
只有当printk打印信息时的loglevel小于console loglevel的值(优先级高于console loglevel),这些信息才会被打印到console上。比如当前的console loglevel是3,那么代码中loglevel为KERN_WARNING,KERN_NOTICE,KERN_INFO,KERN_DEBUG等的log将不会打印.(数字越小,日志优先级越高).

• 方法一:修改日志打印级别

1
echo "7 4 1 7" > /proc/sys/kernel/printk

这种方法的只能对当前环境有效,关机或者重启之后,立马失效.

• 方法二:修改printk.c里面的日志默认级别.kernel/msm-3.18/kernel/printk/printk.c

1
2
3
4
5
6
int console_printk[4] = {
CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */
MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */
CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */
CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */
};

对应的修改console_loglevel,default_message_loglevel,minimum_console_loglevel,default_console_loglevel四个值即可.

printk源码分析

printk模块代码位置:kernel/msm-3.18/kernel/printk/
待补充…
参考资料:
printk打印消息机制
printk实现分析
内核printk的实现分析
tiny4412 串口驱动分析二 — printk的实现
内核日志及printk结构浅析

dmesg log

  1. dmesg log介绍
    dmesg里我们可以查看到开机信息,printk产生的信息等。‘dmesg’命令设备故障的诊断是非常重要的。在‘dmesg’命令的帮助下进行硬件的连接或断开连接操作时,我们可以看到硬件的检测或者断开连接的信息。

  2. 获取dmesg log
    adb shell 后输入dmesg

    1
    2
    3
    4
    [   13.688706] i2c-msm-v2 78b6000.i2c: NACK: slave not responding, ensure its powered: msgs(n:1 cur:0 tx) bc(rx:0 tx:2) 
    mode:FIFO slv_addr:0x1d MSTR_STS:0x0d1300c8 OPER:0x00000090
    [ 13.703484] usb-type-c-pericom 2-001d: i2c write to [1d] failed -107
    [ 13.709767] usb-type-c-pericom 2-001d: i2c access failed

    可以看到每行最开始显示的是一个综括号,里面的数字为timestamp,时间戳,该时间指示的系统从开机到现在的运行时间,单位为s 秒。

  3. dmesg常用的方式
    dmesg -c :在显示的同时,clean掉dmesg缓存中信息
    dmesg -T :以当前时间的方式显示时间信息,而不是上面的那种毫秒的开机时间

    1
    2
    3
    4
    [Wed Jan  3 06:15:22 2018] i2c-msm-v2 78b6000.i2c: NACK: slave not responding, ensure its powered: msgs(n:1 cur:0 tx) bc
    (rx:0 tx:2) mode:FIFO slv_addr:0x1d MSTR_STS:0x0d1300c8 OPER:0x00000090
    [Wed Jan 3 06:15:22 2018] usb-type-c-pericom 2-001d: i2c write to [1d] failed -107
    [Wed Jan 3 06:15:22 2018] usb-type-c-pericom 2-001d: i2c access failed

    由于dmesg日志的输出不适合在一页中完全显示,因此我们使用管道(pipe)将其输出送到more或者less命令单页显示。
    dmesg | more :
    dmesg | less :
    dmesg | grep xxx:
    dmesg -T | more :
    dmesg -T | less :
    dmesg -T | grep xxx:
    dmesg | grep sda: 列出所有被监测到的硬件
    dmesg | head -20:只输出前20行
    dmesg | tail -20:只输出最后20行
    搜索包含特定字符串的被检测到的硬件:
    dmesg | grep -i usb
    dmesg | grep -i dma
    dmesg | grep -i tty
    dmesg | grep -i memory

串口log

常遇到无法开机的状况,这时由于Android还未起来,adb等均无法使用,此时有抓串口的必要。

抓取串口log方式:

  1. Windows系统,使用串口调试工具:SSCOM 5.13.1
  2. Linux操作系统:串口调试工具 minicom
    Android串口log的获取
    串口调试利器–Minicom配置及使用详解

用户态Log的使用

Android系统在用户空间中提供了轻量级的logger日志系统,它是在内核中实现的一种设备驱动,与用户空间的logcat工具配合使用能够方便地跟踪调试程序.在Android系统中,分别为C/C++ 和Java语言提供两种不同的logger访问接口。C/C++日志接口一般是在编写硬件抽象层模块或者编写JNI方法时使用,而Java接口一般是在应用层编写APP时使用。

C/C++模块Log的使用

Android系统中的C/C++日志接口是通过宏来使用的。在system/core/include/android/log.h定义了日志的级别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* Android log priority values, in ascending priority order.
*/
typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;

对于老的Android版本
在system/core/include/cutils/log.h中,定义了对应的宏,如对应于ANDROID_LOG_VERBOSE的宏LOGV:

因此,如果要使用C/C++日志接口,只要定义自己的LOG_TAG宏和包含头文件system/core/include/cutils/log.h就可以了:

1
2
#define LOG_TAG "MY LOG TAG"
#include <cutils/log.h>

就可以了,例如使用LOGV:
1
LOGV("This is the log printed by LOGV in android user space.");

Android8.1.0版本中,log模块进行了调整

ALOG

Java模块Log的使用

Android系统中的Java日志接口.Android系统在Frameworks层中定义了Log接口(frameworks/base/core/java/android/util/Log.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
public final class Log {

/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;

/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;

/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;

/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;

/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;

/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;

/**
* Exception class used to capture a stack trace in {@link #wtf}.
* @hide
*/
public static class TerribleFailure extends Exception {
TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
}

/**
* Interface to handle terrible failures from {@link #wtf}.
*
* @hide
*/
public interface TerribleFailureHandler {
void onTerribleFailure(String tag, TerribleFailure what, boolean system);
}

private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
RuntimeInit.wtf(tag, what, system);
}
};

private Log() {
}

/**
* Send a {@link #VERBOSE} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}

/**
* Send a {@link #VERBOSE} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}

....

/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
*
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
* and place that in /data/local.prop.
*
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23
* for Nougat (7.0) releases (API <= 23) and prior, there is no
* tag limit of concern after this API level.
*/
public static native boolean isLoggable(String tag, int level);

/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
}

/**
* Send an {@link #ERROR} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}

/**
* Send a {@link #ERROR} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
}

/**
* What a Terrible Failure: Report a condition that should never happen.
* The error will always be logged at level ASSERT with the call stack.
* Depending on system configuration, a report may be added to the
* {@link android.os.DropBoxManager} and/or the process may be terminated
* immediately with an error dialog.
* @param tag Used to identify the source of a log message.
* @param msg The message you would like logged.
*/
public static int wtf(String tag, String msg) {
return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
}

/**
* Like {@link #wtf(String, String)}, but also writes to the log the full
* call stack.
* @hide
*/
public static int wtfStack(String tag, String msg) {
return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
}

/**
* What a Terrible Failure: Report an exception that should never happen.
* Similar to {@link #wtf(String, String)}, with an exception to log.
* @param tag Used to identify the source of a log message.
* @param tr An exception to log.
*/
public static int wtf(String tag, Throwable tr) {
return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
}

/**
* What a Terrible Failure: Report an exception that should never happen.
* Similar to {@link #wtf(String, Throwable)}, with a message as well.
* @param tag Used to identify the source of a log message.
* @param msg The message you would like logged.
* @param tr An exception to log. May be null.
*/
public static int wtf(String tag, String msg, Throwable tr) {
return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
}

static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
boolean system) {
TerribleFailure what = new TerribleFailure(msg, tr);
// Only mark this as ERROR, do not use ASSERT since that should be
// reserved for cases where the system is guaranteed to abort.
// The onTerribleFailure call does not always cause a crash.
int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
sWtfHandler.onTerribleFailure(tag, what, system);
return bytes;
}

static void wtfQuiet(int logId, String tag, String msg, boolean system) {
TerribleFailure what = new TerribleFailure(msg, null);
sWtfHandler.onTerribleFailure(tag, what, system);
}

/**
* Sets the terrible failure handler, for testing.
*
* @return the old handler
*
* @hide
*/
public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
if (handler == null) {
throw new NullPointerException("handler == null");
}
TerribleFailureHandler oldHandler = sWtfHandler;
sWtfHandler = handler;
return oldHandler;
}

/**
* Handy function to get a loggable stack trace from a Throwable
* @param tr An exception to log
*/
public static String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}

// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}

StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 256);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
}

/**
* Low-level logging call.
* @param priority The priority/type of this log message
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
return println_native(LOG_ID_MAIN, priority, tag, msg);
}

/** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;

/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);

/**
* Return the maximum payload the log daemon accepts without truncation.
* @return LOGGER_ENTRY_MAX_PAYLOAD.
*/
private static native int logger_entry_max_payload_native();

/**
* Helper function for long messages. Uses the LineBreakBufferedWriter to break
* up long messages and stacktraces along newlines, but tries to write in large
* chunks. This is to avoid truncation.
* @hide
*/
public static int printlns(int bufID, int priority, String tag, String msg,
Throwable tr) {
ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
// Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
// and the length of the tag.
// Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
// is too expensive to compute that ahead of time.
int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- 2 // Two terminators.
- (tag != null ? tag.length() : 0) // Tag length.
- 32; // Some slack.
// At least assume you can print *some* characters (tag is not too large).
bufferSize = Math.max(bufferSize, 100);

LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);

lbbw.println(msg);

if (tr != null) {
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
break;
}
if (t instanceof DeadSystemException) {
lbbw.println("DeadSystemException: The system died; "
+ "earlier logs will point to the root cause");
break;
}
t = t.getCause();
}
if (t == null) {
tr.printStackTrace(lbbw);
}
}

lbbw.flush();

return logWriter.getWritten();
}

/**
* PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
* a JNI call during logging.
*/
static class PreloadHolder {
public final static int LOGGER_ENTRY_MAX_PAYLOAD =
logger_entry_max_payload_native();
}

/**
* Helper class to write to the logcat. Different from LogWriter, this writes
* the whole given buffer and does not break along newlines.
*/
private static class ImmediateLogWriter extends Writer {

private int bufID;
private int priority;
private String tag;

private int written = 0;

/**
* Create a writer that immediately writes to the log, using the given
* parameters.
*/
public ImmediateLogWriter(int bufID, int priority, String tag) {
this.bufID = bufID;
this.priority = priority;
this.tag = tag;
}

public int getWritten() {
return written;
}

@Override
public void write(char[] cbuf, int off, int len) {
// Note: using String here has a bit of overhead as a Java object is created,
// but using the char[] directly is not easier, as it needs to be translated
// to a C char[] for logging.
written += println_native(bufID, priority, tag, new String(cbuf, off, len));
}

@Override
public void flush() {
// Ignored.
}

@Override
public void close() {
// Ignored.
}
}
}

因此,如果要使用Java日志接口,只要在类中定义的LOG_TAG常量和引用android.util.Log就可以了:

1
2
private static final String LOG_TAG = "MY_LOG_TAG";
Log.i(LOG_TAG, "This is the log printed by Log.i in android user space.");

用户态log查看工具: logcat

★ Tips:logcat输出log的级别控制

logcat的使用

参考资料
https://zhuanlan.zhihu.com/p/24372024

Framework中Log功能解析

AP侧的Log系统根据log类型,分为main,system,radio,crash,events.
frameworks/base/core/java/android/util/Log.java //用于记录main log
frameworks/base/core/java/android/util/Slog.java //用于记录framework log
frameworks/base/telephony/java/android/telephony/Rlog.java //用于记录radio log
frameworks/base/core/java/com/android/internal/os/RuntimeInit.java //内部记录了crash log
frameworks/base/core/java/android/util/EventLog.java //用于Event log的使用

上述5种类型的log可以分为3大类:
第一类:main/system/radio
第二类:event
第三类:crash

main/system/radio log

从Log.java,Slog.java,Rlog.java三个文件来看,这三种log最大的区别就是LOG_ID不一样,即在framework侧已经区分了写入不同的缓冲区,如下:
Log.java,通过println_native写入LOG_ID_MAIN缓冲区

1
2
3
4
5
6
7
8
9
/**
* Send a {@link #VERBOSE} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}

Slog.java,通过println_native写入LOG_ID_SYSTEM缓冲区

1
2
3
public static int v(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
}

Rlog.java,通过println_native写入LOG_ID_RADIO缓冲区

1
2
3
public static int v(String tag, String msg) {
return Log.println_native(Log.LOG_ID_RADIO, Log.VERBOSE, tag, msg);
}

Slog和Rlog都是调用Log里面的println_native函数,只是缓冲区ID不一样而已.
对于这三类log,主要分析Log.java.Log主要实现以下函数:
• 定义了Log的5中优先级,分别是 VERBOSE > DEBUG > INFO > WARN > ERROR > ASSERT,数字越大优先级越高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    /**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;

/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;

/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;

/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;

/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;

/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
```

• 带Tag和msg两个参数的实现,最后通过JNI调用println_native函数实现,后面继续分析.
```java
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}

• 带Tag,msg以及Throwable三个参数的实现,可以在这里查看Throwable的源码,多了一个异常参数.在输出log信息的同时,如果报了异常,同时将异常信息输出.

1
2
3
public static int v(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}

通过printlns函数实现,继续追踪printlns函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Helper function for long messages. Uses the LineBreakBufferedWriter to break
* up long messages and stacktraces along newlines, but tries to write in large
* chunks. This is to avoid truncation.
* @hide
*/
public static int printlns(int bufID, int priority, String tag, String msg,
Throwable tr) {
ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
// Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
// and the length of the tag.
// Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
// is too expensive to compute that ahead of time.
int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- 2 // Two terminators.
- (tag != null ? tag.length() : 0) // Tag length.
- 32; // Some slack.
// At least assume you can print *some* characters (tag is not too large).
bufferSize = Math.max(bufferSize, 100);

LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);

lbbw.println(msg); //println函数后面还是调用了println_native

if (tr != null) {
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
break;
}
if (t instanceof DeadSystemException) {
lbbw.println("DeadSystemException: The system died; "
+ "earlier logs will point to the root cause");
break;
}
t = t.getCause();
}
if (t == null) {
tr.printStackTrace(lbbw);
}
}

lbbw.flush();

return logWriter.getWritten();
}

由于带有异常信息,一般异常信息都很长很大,而且里面有换行符,而每条log信息长度最大只能是4k个字节,所以为了避免信息被截断,在写入缓冲区之前,需要对这类信息进行预处理,也就是进行切割,分成多行显示.使用LineBreakBufferedWriter类刚好符合这样的要求,将这样的msg,识别出换行符之后,切割成多块,分行显示.

• 实现println函数,也就是说我们也可以用Log.println函数来实现log的输出,不过使用的比较少.

1
2
3
4
5
6
7
8
9
10
11
/**
* Low-level logging call.
* @param priority The priority/type of this log message
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
return println_native(LOG_ID_MAIN, priority, tag, msg);
}

• 实现了wtf的多个重载方法,wtf的解释是What a Terrible Failure: Report a condition that should never happen.The error will always be logged at level ASSERT with the call stack.Depending on system configuration, a report may be added to the { @link android.os.DropBoxManager } and/or the process may be terminated immediately with an error dialog. 表示此时遇到了一个不可能发生,严重而又无法挽救的情况,对应的是ASSERT级别的日志,一般用于系统应用中,会用在Dropbox日志以及立即杀死进程弹框等场景下.这些应用留给后面讲Dropbox log以及ANR等具体使用的时候现场分析.

Event log

第二类的EventLog,可以在Java和C/C++代码中使用,用于特殊事件的打点,这里说的事件不仅仅值的输入事件,还包括特殊场景的特殊状态,比如Activity,Window等状态.由于Eventlog的实现机制和上面第一类的不一样,下面的章节会单独将Eventlog作为一节来讲,请往下面章节看.本节主要分析EventLog.java的功能.

a)实现writeEvent函数,通过JNI,调用到底层的代码,最终将event写入到/dev/log/events(之前版本写入的是这里,现在的8.1.0版本不再是这里了)
b)实现readEvents函数,通过JNI,调用到底层代码, 具体使用场景还不没找,提供了方法来读取对应type的eventlog
c)实现getTagName函数,通过给出的ID,返回Tag的名称,主要通过在system/etc/event-log-tags文件里面进行查找实现,供Java侧其他模块调用
d)实现getTagCode函数,通过给出的Tag名称,返回Tag的ID,主要通过在system/etc/event-log-tags文件里面进行查找实现,供Java侧其他模块调用
e)实现内部静态类Event,这个是8.1.0之前的老版本的EventLog的实现.暂不关注.

crash log

对于第三类crash log.用于捕获应用的异常.
RuntimeInit.java是通过app_server启动,看frameworks/base/cmds/app_process/app_main.cpp代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char* const argv[])
{
...
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
}

我们来看一下frameworks/base/core/java/com/android/internal/os/RuntimeInit.java,从上面代码可以看出,RuntimeInit是所有APP 进程的入口,AP侧的所有进程都是从这里启动,负责进程初始化的,继续看RuntimeInit代码实现的功能,在RuntimeInit.java中Clog_e函数里面可以看到crash log是输出到LOG_ID_CRASH缓冲区里面的,当然应用crash时的日志级别也是ERROR级的,如下:

1
2
3
private static int Clog_e(String tag, String msg, Throwable tr) {
return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
}

RuntimeInit里面有两个内部类LoggingHandler和KillApplicationHandler分别用于捕获线程和Application的异常信息并输出log.下面分别来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Logs a message when a thread encounters an uncaught exception. By
* default, {@link KillApplicationHandler} will terminate this process later,
* but apps can override that behavior.
*/
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
// Don't re-enter if KillApplicationHandler has already run
if (mCrashing) return;
if (mApplicationObject == null) {
// The "FATAL EXCEPTION" string is still used on Android even though
// apps can set a custom UncaughtExceptionHandler that renders uncaught
// exceptions non-fatal.
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
// The "FATAL EXCEPTION" string is still used on Android even though
// apps can set a custom UncaughtExceptionHandler that renders uncaught
// exceptions non-fatal.
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
final String processName = ActivityThread.currentProcessName();
if (processName != null) {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
Clog_e(TAG, message.toString(), e);
}
}
}

从代码中可以看到,当ApplicationObject为空的时候,表示Binder出了问题,此时应该是系统进程出问题了,会打印,*** FATAL EXCEPTION IN SYSTEM PROCESS: 等字符串.
如果线程遇到了其他异常,会打印FATAL EXCEPTION:,以及当前进程名称,进程ID号.如下有一个实例:

1
2
3
4
5
6
7
8
9
10
06-14 11:08:30.924 10048  2484  2585 E AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: Process: com.emoji.keyboard.touchpal.go, PID: 2484
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: java.lang.IllegalStateException: SharedPreferences in credential encrypted storage are not available until after user is unlocked
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:391)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:376)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:167)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at com.facebook.internal.FetchedAppSettingsManager$1.run(SourceFile:106)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
06-14 11:08:30.924 10048 2484 2585 E AndroidRuntime: at java.lang.Thread.run(Thread.java:764)

继续看KillApplicationHandler的异常捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Handle application death from an uncaught exception. The framework
* catches these for the main threads, so this should only matter for
* threads created by applications. Before this method runs,
* {@link LoggingHandler} will already have logged details.
*/
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
try {
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;

// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}

// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}

可以看到,调用了ActivityManager中的handleApplicationCrash方法来处理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Used by {@link com.android.internal.os.RuntimeInit} to report when an application crashes.
* The application process will exit immediately after this call returns.
* @param app object of the crashing app, null for the system server
* @param crashInfo describing the exception
*/
public void handleApplicationCrash(IBinder app,
ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
ProcessRecord r = findAppProcess(app, "Crash");
final String processName = app == null ? "system_server"
: (r == null ? "unknown" : r.processName);

handleApplicationCrashInner("crash", r, processName, crashInfo);
}

/* Native crash reporting uses this inner version because it needs to be somewhat
* decoupled from the AM-managed cleanup lifecycle
*/
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
crashInfo.throwFileName,
crashInfo.throwLineNumber);

addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);

mAppErrors.crashApplication(r, crashInfo);
}

可以看到发生crash时,也往event log里面写入了crash事件,接着干两件事
一:addErrorToDropBox,addErrorToDropBox函数是将crash/WTF/ANR/Low on memory 等信息收集并拼接处头部信息后输出到dropbox文件中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Write a description of an error (crash, WTF, ANR) to the drop box.
* @param eventType to include in the drop box tag ("crash", "wtf", etc.)
* @param process which caused the error, null means the system server
* @param activity which triggered the error, null if unknown
* @param parent activity related to the error, null if unknown
* @param subject line related to the error, null if absent
* @param report in long form describing the error, null if absent
* @param dataFile text file to include in the report, null if none
* @param crashInfo giving an application stack trace, null if absent
*/
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, ActivityRecord activity,
ActivityRecord parent, String subject,
final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo) {
// NOTE -- this must never acquire the ActivityManagerService lock,
// otherwise the watchdog may be prevented from resetting the system.
...
}

二:mAppErrors.crashApplication(r, crashInfo);我们到AppErrors.java中看看crashApplication,可以看到主要目的是对发生crash的APP,杀死进程后弹出一个dialog框告知用户or not.详细的处理逻辑见crashApplicationInner函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Bring up the "unexpected error" dialog box for a crashing app.
* Deal with edge cases (intercepts from instrumented applications,
* ActivityController, error intent receivers, that sort of thing).
* @param r the application crashing
* @param crashInfo describing the failure
*/
void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();

final long origId = Binder.clearCallingIdentity();
try {
crashApplicationInner(r, crashInfo, callingPid, callingUid);
} finally {
Binder.restoreCallingIdentity(origId);
}
}

JNI中log功能解析

在上一节中总结了Framework层中提供了一些log方法供Java侧来使用,最后都调用到了frameworks/base/core/java/android/util/Log.java里面的println_native这个native函数,本节就开始往下追踪println_native函数的实现.
再讲两个Log.java里面我们看到的细节:
一、log总共有5个缓冲区,如下:

1
2
3
4
5
/** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;

二、共调用了三个native方法,println_native,logger_entry_max_payload_native,isLoggable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);

/**
* Return the maximum payload the log daemon accepts without truncation.
* @return LOGGER_ENTRY_MAX_PAYLOAD.
*/
private static native int logger_entry_max_payload_native();
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
*
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
* and place that in /data/local.prop.
*
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23
* for Nougat (7.0) releases (API <= 23) and prior, there is no
* tag limit of concern after this API level.
*/
public static native boolean isLoggable(String tag, int level);

logger_entry_max_payload_native函数用于获取每条log的缓冲区大小,避免太长的log被截断而存入不完整.
isLoggable函数是在7.0之后的版本上加入的,可以获取到某个tag相对应的Level级别的log能否打印,可以通过在local.prop文件中设置这个属性.具体怎么设置,还需要继续往下追踪代码.

接下来继续往下追踪println_native的流程,到frameworks/base/core/jni/android_util_Log.cpp

1
2
3
4
5
6
7
8
9
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
{ "logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

JNI中,Log.java中的三个native方法,分别通过android_util_Log_isLoggable,android_util_Log_println_native,
android_util_Log_logger_entry_max_payload_native实现,我们先来看看frameworks/base/core/jni这个目录中Android这个编译文件,在这里我们看到需要的与log相关的依赖库有liblog,libnativehelper,libutils,libcutils,libandroidfw,libbase等这些lib库文件.

一、查看android_util_Log_isLoggable函数的实现,最后调用的是__android_log_is_loggable函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
if (tag == NULL) {
return false;
}

const char* chars = env->GetStringUTFChars(tag, NULL);
if (!chars) {
return false;
}

jboolean result = isLoggable(chars, level);

env->ReleaseStringUTFChars(tag, chars);
return result;
}
static jboolean isLoggable(const char* tag, jint level) {
return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}

二、查看android_util_Log_println_native函数的实现,最后调用的是__android_log_buf_write函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;

if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}

if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}

if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);//取出tag
msg = env->GetStringUTFChars(msgObj, NULL);//取出msg

int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);//往缓冲区写

if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);

return res;
}

log_JNI-liblog-lod流程图

三、查看android_util_Log_logger_entry_max_payload_native函数,最后取的是LOGGER_ENTRY_MAX_PAYLOAD的值.

1
2
3
4
5
6
7
8
9
/*
* In class android.util.Log:
* private static native int logger_entry_max_payload_native()
*/
static jint android_util_Log_logger_entry_max_payload_native(JNIEnv* env ATTRIBUTE_UNUSED,
jobject clazz ATTRIBUTE_UNUSED)
{
return static_cast<jint>(LOGGER_ENTRY_MAX_PAYLOAD);
}

本文主要研究log相关的,所以重点还是只研究liblog这个库文件,具体见下一节.

运行时库层liblog模块log功能解析

liblog:system/core/liblog
功能:Android Internal NDK logger interfaces Android内部的NDK logger模块的接口.提供一些公共函数接口供其他模块调用,
目前了解到的一下模块需要用到liblog库:
• logd
• logcat
• system/core/libvndksupport: VNDK -vendor NDK 各个芯片厂商自定义的NDK
• frameworks/native/services/audiomanager
等等模块都需要用liblog库文件来处理log.

阅读README文件.如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
DESCRIPTION
liblog represents an interface to the volatile Android Logging system
for NDK (Native) applications and libraries. Interfaces for either
writing or reading logs. The log buffers are divided up in Main, Sys‐
tem, Radio and Events sub-logs.
liblog是Android Log系统用于NDK应用或者lib库的接口,此接口提供了读和写log的方法,log
缓冲区被分为类Main.system.radio.event等几个部分.

The logging interfaces are a series of macros, all of which can be
overridden individually in order to control the verbosity of the appli‐
cation or library. [ASR]LOG[VDIWE] calls are used to log to BAsic,
System or Radio sub-logs in either the Verbose, Debug, Info, Warning or
Error priorities. [ASR]LOG[VDIWE]_IF calls are used to perform thus
based on a condition being true. IF_ALOG[VDIWE] calls are true if the
current LOG_TAG is enabled at the specified priority. LOG_ALWAYS_FATAL
is used to ALOG a message, then kill the process. LOG_FATAL call is a
variant of LOG_ALWAYS_FATAL, only enabled in engineering, and not
release builds. ALOG_ASSERT is used to ALOG a message if the condition
is false; the condition is part of the logged message.
LOG_EVENT_(INT|LONG) is used to drop binary content into the Events
sub-log.
译:log接口就是一系列的宏,在每个应用或者库中为了更详细的控制log的输出,在各个应用或者库中
可以对这些宏进行单独的覆盖处理.
[ASR]LOG[VDIWE] 类型:如ALOGV(format, ...).可以用于打印main,system,radio类
的各种级别的log.
[ASR]LOG[VDIWE]_IF 类型:如ALOGV_IF(cond, format, ...).可以用于打印满足cond
条件的时候输出log.
IF_ALOG[VDIWE] 类型:如IF_ALOGD().当当前的LOG_TAG在特殊优先级下运行使用时,可以正
常使用.
LOG_ALWAYS_FATAL 类型:如LOG_ALWAYS_FATAL(format, ...).用于打印一条kill进程
log.
LOG_FATAL 类型:如LOG_FATAL(format, ...).与LOG_ALWAYS_FATAL一样的功能,不过它
只在Eng版本中打印,release版本中不会打印.
ALOG_ASSERT 类型:如ALOG_ASSERT(cond, format, ...).当cond不满足的时候打印.
LOG_EVENT_INT 类型:如LOG_EVENT_INT(tag,value).用于打印二进制内容到Eventlog中

The log reading interfaces permit opening the logs either singly or
multiply, retrieving a log entry at a time in time sorted order,
optionally limited to a specific pid and tail of the log(s) and finally
a call closing the logs. A single log can be opened with android_log‐
ger_list_open; or multiple logs can be opened with android_log‐
ger_list_alloc, calling in turn the android_logger_open for each log
id. Each entry can be retrieved with android_logger_list_read. The
log(s) can be closed with android_logger_list_free. The logs should be
opened with an ANDROID_LOG_RDONLY mode. ANDROID_LOG_NONBLOCK mode
will report when the log reading is done with an EAGAIN error return
code, otherwise the android_logger_list_read call will block for new
entries.
译:log的reading接口可以读取通过时间排序收到的一条log.
单条log可以通过 android_logger_list_open 函数打开,多条log可以通过android_log‐
ger_list_alloc 调用 android_logger_list_open 打开.每条log都可以被 android_l-
ogger_list_read 函数检索. 可以通过 android_logger_list_free 关闭回收. log必须
在 ANDROID_LOG_RDONLY 的只读模式打开. 当log读取完成后 ANDROID_LOG_NONBLOCK 模
式将会以 EAGAIN 形式返回,否则 android_logger_list_read 将会阻止读取新的log.

The ANDROID_LOG_WRAP mode flag to the android_logger_list_alloc_time
signals logd to quiesce the reader until the buffer is about to prune
at the start time then proceed to dumping content.


The ANDROID_LOG_PSTORE mode flag to the android_logger_open is used to
switch from the active logs to the persistent logs from before the last
reboot.
译:ANDROID_LOG_PSTORE 模式标识用于从上次重启之后使用 android_logger_open 打开
log从 active logs 切换到 persistent logs.

The value returned by android_logger_open can be used as a parameter to
the android_logger_clear function to empty the sub-log. It is recom‐
mended to only open log ANDROID_LOG_WRONLY in that case.
译:android_logger_open 函数的返回值可以作为 android_logger_clear 函数的参数来
清空打开的log信息.仅推荐在 ANDROID_LOG_WRONLY 只写模式下打开log时使用.

The value returned by android_logger_open can be used as a parameter to
the android_logger_get_log_(size|readable_size|version) to retrieve the
sub-log maximum size, readable size and log buffer format protocol ver‐
sion respectively. android_logger_get_id returns the id that was used
when opening the sub-log. It is recommended to open the log
ANDROID_LOG_RDONLY in these cases.
译:android_logger_open 函数的返回值可以作为 android_logger_get_log_ 函数的参
数.仅推荐在 ANDROID_LOG_RDONLY 只读模式下启动.

android_set_log_transport() selects transport filters. Argument is
either LOGGER_DEFAULT, LOGGER_LOGD, LOGGER_NULL or LOGGER_LOCAL. Log to
logger daemon for default or logd, drop contents on floor, or log into
local memory respectively. Both android_set_log_transport()
and android_get_log_transport() return the current transport mask, or
a negative errno for any problems.
译:

ERRORS
If messages fail, a negative error code will be returned to the caller.

The -ENOTCONN return code indicates that the logger daemon is stopped.

The -EBADF return code indicates that the log access point can not be
opened, or the log buffer id is out of range.

For the -EAGAIN return code, this means that the logging message was
temporarily backed-up either because of Denial Of Service (DOS) logging
pressure from some chatty application or service in the Android system,
or if too small of a value is set in /proc/sys/net/unix/max_dgram_qlen.
To aid in diagnosing the occurence of this, a binary event from liblog
will be sent to the log daemon once a new message can get through
indicating how many messages were dropped as a result. Please take
action to resolve the structural problems at the source.

It is generally not advised for the caller to retry the -EAGAIN return
code as this will only make the problem(s) worse and cause your
application to temporarily drop to the logger daemon priority, BATCH
scheduling policy and background task cgroup. If you require a group of
messages to be passed atomically, merge them into one message with
embedded newlines to the maximum length LOGGER_ENTRY_MAX_PAYLOAD.

Other return codes from writing operation can be returned. Since the
library retries on EINTR, -EINTR should never be returned.

注意:下面这三个目录中的代码
system/core/include/android
system/core/include/log
system/core/include/private
和 liblog模块中下面三个目录下面的代码一模一样
system/core/liblog/include/android
system/core/liblog/include/log
system/core/liblog/include/private
同时也和system/core/liblog/include_vndk一模一样,
怀疑这三个地方的文件是软连接.我们到system/core/include/android/log.h用ls -al查看,果然是软连接到了system/core/liblog/include/log/log.h文件.

Q:为何要做这三个目录的软连接?

我们从system/core/liblog/include/android/log.h里面看看关于liblog的一些说明.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/******************************************************************
*
* IMPORTANT NOTICE:
*
* This file is part of Android's set of stable system headers
* exposed by the Android NDK (Native Development Kit) since
* platform release 1.5
*
* Third-party source AND binary code relies on the definitions
* here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
*
* - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
* - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
* - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
* - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
*/

/*
* Support routines to send messages to the Android in-kernel log buffer,
* which can later be accessed through the 'logcat' utility.
* //liblog支持往kernel的log缓冲区发送消息,这些消息可以通过logcat工具来进行读取.
*
* Each log message must have
* - a priority
* - a log tag
* - some text
* //每条log信息必须由以下三部分组成
* // a)优先级
* // b)tag 标签
* // c)text 内容
*
* The tag normally corresponds to the component that emits the log message,
* and should be reasonably small.
* //Tag主要用于标识log发生的组件或者地方,不易过长.
*
* Log message text may be truncated to less than an implementation-specific
* limit (e.g. 1023 characters max).
* //log消息文本可能会被截断为小于规定的长度,比如若规定最大字符串长度为1023个字节,超过的话,可能会被截断.
*
* Note that a newline character ("\n") will be appended automatically to your
* log message, if not already there. It is not possible to send several
* messages and have them appear on a single line in logcat.
* //如果没有换行符的话,会自动进行换行,不可能在单行中显示多条log信息.
*
* PLEASE USE LOGS WITH MODERATION://请尽量避免打log
*
* - Sending log messages eats CPU and slow down your application and the
* system.//发送log信息将消耗CPU而且会降低应用的性能.
*
* - The circular log buffer is pretty small (<64KB), sending many messages
* might push off other important log messages from the rest of the system.
* //log的缓冲区非常小,小于64KB,所以如果频繁发送太多的消息,可能会冲掉之前的重要消息.
*
* - In release builds, only send log messages to account for exceptional
* conditions.//在release版本中,仅发送特殊限定的一些log
*
* NOTE: These functions MUST be implemented by /system/lib/liblog.so
*/

logd模块功能解析

logd 是Android L版本提出来的概念,其作用有:
一:与liblog交互,保存Android运行期间的log.在Android L之前,log由kernel的ring buffer 保存,在Android L之后,log保存在用户空间。
二:与logcat交互,接收从logcat传来的命令,将log从logd传给logcat.

logd代码位置:system/core/logd

logd模块相关的prop属性值

1
2


logd 进程的启动

参考资料:
android logd 原理及实现
Android L日志系统1——logd
Android6.0 Log的工作机制

Logcat源码解析

logcat源码位于:system/core/logcat/目录
Logcat模块代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
logcat
└── inclide
| └── log
| └── getopt.h
| └── log.h
└── Android.mk
└── event.logtags
└── getopt_long.cpp
└── logcat_main.cpp
└── logcat_system.cpp
└── logcat.cpp
└── logcatd_main.cpp
└── logcatd.rc
└── logpersist
└── .clang.format

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
logcat
├── Android.mk
├── event.logtags
├── getopt_long.cpp
├── include
│   └── log
│   ├── getopt.h
│   └── logcat.h
├── logcat.cpp
├── logcatd_main.cpp
├── logcatd.rc
├── logcat_main.cpp
├── logcat_system.cpp
├── logpersist
├── MODULE_LICENSE_APACHE2
├── NOTICE
└── tests
├── Android.mk
├── exec_benchmark.cpp
├── liblogcat_test.cpp
├── logcat_benchmark.cpp
├── logcatd_test.cpp
└── logcat_test.cpp

Q: 为何可以用adb 后面带 logcat 的方式来抓取log?
ADB实现解析
Android adb实现原理

模块文件分析

1) Android.mk
从Android.mk文件来看,编译生成了logcat和logcatd两个可执行bin文件,一个liblogcat共享库文件,和一个logpersist.start文件
logcat 作用: 入口函数是logcat_main.cpp
logcatd 作用: 用于保存log的服务,入口函数是logcatd_main.cpp
liblogcat 作用: 给logcat test使用
logpersist.start : 依据logpersist会在system/bin目录,生成logpersist.start logpersist.stop logpersist.cat

2)event.logtags
定义了一些Event log

3)getopt_long.cpp
命令行解析函数,支持长选项解析,用于解析logcat命令的辅助工具.

4)logcat_main.cpp

参考资料
Android日志系统Logcat源代码简要分析

Logwrapper 模块解析

Logger 驱动模块解析

在用户态中的log是由logger驱动实现,使用logcat工具完成log的查看和收集.本节将关注Logger驱动的实现原理.
Logger驱动程序主要由两个文件构成,分别是:
kernel/drivers/staging/android/logger.c
kernel/drivers/staging/android/logger.h

Logger系统数据结构

先来看一下logger.h头文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#ifndef _LINUX_LOGGER_H
#define _LINUX_LOGGER_H

#include <linux/types.h>
#include <linux/ioctl.h>

/**
* struct user_logger_entry_compat - defines a single entry that is given to a logger
* @len: The length of the payload //有效负载长度
* @__pad: Two bytes of padding that appear to be required //两个字节的空格
* @pid: The generating process' process ID //进程ID
* @tid: The generating process' thread ID //线程ID
* @sec: The number of seconds that have elapsed since the Epoch //秒数
* @nsec: The number of nanoseconds that have elapsed since @sec //纳秒数
* @msg: The message that is to be logged //log的内容
*
* The userspace structure for version 1 of the logger_entry ABI.
* This structure is returned to userspace unless the caller requests
* an upgrade to a newer ABI version.
* 用户空间中一条log的结构
*/
struct user_logger_entry_compat {
__u16 len;
__u16 __pad;
__s32 pid;
__s32 tid;
__s32 sec;
__s32 nsec;
char msg[0];
};

/**
* struct logger_entry - defines a single entry that is given to a logger
* @len: The length of the payload //有效负载长度
* @hdr_size: sizeof(struct logger_entry_v2) //
* @pid: The generating process' process ID //进程ID
* @tid: The generating process' thread ID // 线程ID
* @sec: The number of seconds that have elapsed since the Epoch //秒
* @nsec: The number of nanoseconds that have elapsed since @sec //纳秒
* @euid: Effective UID of logger //User Identifier 用户ID
* @msg: The message that is to be logged
*
* The structure for version 2 of the logger_entry ABI.
* This structure is returned to userspace if ioctl(LOGGER_SET_VERSION)
* is called with version >= 2
*/
struct logger_entry {
__u16 len;
__u16 hdr_size;
__s32 pid;
__s32 tid;
__s32 sec;
__s32 nsec;
kuid_t euid;
char msg[0];
};

#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
#define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */
#define LOGGER_LOG_MAIN "log_main" /* everything else */

#define LOGGER_ENTRY_MAX_PAYLOAD 4076 //每条日志记录最大长度4076字节

#define __LOGGERIO 0xAE

#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */
#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */
#define LOGGER_GET_VERSION _IO(__LOGGERIO, 5) /* abi version */
#define LOGGER_SET_VERSION _IO(__LOGGERIO, 6) /* abi version */

#endif /* _LINUX_LOGGER_H */

我们看到有两个结构体 user_logger_entry_compat 和 logger_entry,均用来描述一条Log记录.

struct user_logger_entry_compat 是一个用于描述一条Log记录的结构体。len成员变量记录了这条记录的有效负载的长度,有效负载指定的日志记录本身的长度,但是不包括用于描述这个记录的struct logger_entry结构体。回忆一下我们调用android.util.Log接口来使用日志系统时,会指定日志的优先级别Priority、Tag字符串以及Msg字符串,Priority + Tag + Msg三者内容的长度加起来就是记录的有效负载长度了。__pad成员变量是用来对齐结构体的。pid和tid成员变量分别用来记录是哪条进程和线程写入了这条记录。sec和nsec成员变量记录日志写的时间。msg成员变量记录的就有效负载的内容了,它的大小由len成员变量来确定。

#define LOGGER_ENTRY_MAX_PAYLOAD 4076 表示每条日志记录最大长度4076字节=(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* struct logger_log - represents a specific log, such as 'main' or 'radio'
* @buffer: The actual ring buffer
* @misc: The "misc" device representing the log
* @wq: The wait queue for @readers
* @readers: This log's readers
* @mutex: The mutex that protects the @buffer
* @w_off: The current write head offset
* @head: The head, or location that readers start reading at.
* @size: The size of the log
* @logs: The list of log channels
*
* This structure lives from module insertion until module removal, so it does
* not need additional reference counting. The structure is protected by the
* mutex 'mutex'.
*/
struct logger_log {
unsigned char *buffer;
struct miscdevice misc;
wait_queue_head_t wq;
struct list_head readers;
struct mutex mutex;
size_t w_off;
size_t head;
size_t size;
struct list_head logs;
};

static LIST_HEAD(log_list);


/**
* struct logger_reader - a logging device open for reading
* @log: The associated log
* @list: The associated entry in @logger_log's list
* @r_off: The current read head offset.
* @r_all: Reader can read all entries
* @r_ver: Reader ABI version
*
* This object lives from open to release, so we don't need additional
* reference counting. The structure is protected by log->mutex.
*/
struct logger_reader {
struct logger_log *log;
struct list_head list;
size_t r_off;
bool r_all;
int r_ver;
};

结构体struct logger_log就是真正用来保存日志的地方了。buffer成员变量变是用保存日志信息的内存缓冲区,它的大小由size成员变量确定。从misc成员变量可以看出,logger驱动程序使用的设备属于misc类型的设备,通过在Android模拟器上执行cat /proc/devices命令(可参考在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文),可以看出,misc类型设备的主设备号是10。关于主设备号的相关知识,可以参考Android学习启动篇一文中提到的Linux Driver Development一书。wq成员变量是一个等待队列,用于保存正在等待读取日志的进程。readers成员变量用来保存当前正在读取日志的进程,正在读取日志的进程由结构体logger_reader来描述。mutex成员变量是一个互斥量,用来保护log的并发访问。可以看出,这里的日志系统的读写问题,其实是一个生产者-消费者的问题,因此,需要互斥量来保护log的并发访问。 w_off成员变量用来记录下一条日志应该从哪里开始写。head成员变量用来表示打开日志文件中,应该从哪一个位置开始读取日志

结构体struct logger_reader用来表示一个读取日志的进程,log成员变量指向要读取的日志缓冲区。list成员变量用来连接其它读者进程。r_off成员变量表示当前要读取的日志在缓冲区中的位置。

struct logger_log结构体中用于保存日志信息的内存缓冲区buffer是一个循环使用的环形缓冲区,缓冲区中保存的内容是以struct logger_entry为单位的,每个单位的组成为:

struct logger_entry | priority | tag | msg

由于是内存缓冲区buffer是一个循环使用的环形缓冲区,给定一个偏移值,它在buffer中的位置由下logger_offset来确定:

#define logger_offset(n)          ((n) & (log->size - 1))

Logger模块的初始化过程分析

继续看logger.c文件,定义了三个日志设备:
待补充

日志系统读取

待补充

日志系统写入

待补充

Eventlog解析

在调试分析Android的过程中,比较常用的地查看EventLog,非常简洁明了地展现当前Activity各种状态,以及Window状态等,看完本节之后,如果在某个模块调试过程中,有几个比较关键的状态值需要关注,也可以自己定义一些Event事件,最后从eventlog中获取状态值.

本节将从EventLog在代码中的使用开始解析EventLog的流程.

Eventlog的使用

在代码中搜索*.logtags文件,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
./system/bt/EventLogTags.logtags
./system/core/logd/event.logtags
./system/core/libsysutils/EventLogTags.logtags
./system/core/liblog/event.logtags
./system/core/logcat/event.logtags
./system/core/storaged/EventLogTags.logtags
./packages/apps/TimeZoneUpdater/src/main/com/android/timezone/updater/EventLogTags.logtags
./packages/apps/QuickSearchBox/src/com/android/quicksearchbox/EventLogTags.logtags
./packages/apps/Settings/src/com/android/settings/EventLogTags.logtags
./packages/services/Telephony/src/com/android/phone/EventLogTags.logtags
./packages/providers/CalendarProvider/src/com/android/providers/calendar/EventLogTags.logtags
./packages/providers/ContactsProvider/src/com/android/providers/contacts/EventLogTags.logtags
./frameworks/base/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
./frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
./frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags
./frameworks/base/services/core/java/com/android/server/EventLogTags.logtags
./frameworks/base/core/java/com/android/internal/logging/EventLogTags.logtags
./frameworks/base/core/java/android/content/EventLogTags.logtags
./frameworks/base/core/java/android/app/admin/SecurityLogTags.logtags
./frameworks/base/core/java/android/speech/tts/EventLogTags.logtags
./frameworks/base/core/java/android/net/EventLogTags.logtags
./frameworks/base/core/java/android/webkit/EventLogTags.logtags
./frameworks/native/services/surfaceflinger/EventLog/EventLogTags.logtags
./frameworks/ex/common/java/com/android/common/GoogleLogTags.logtags
./frameworks/opt/telephony/src/java/com/android/internal/telephony/EventLogTags.logtags

从上面的结果可以看到,有很多的模块都有使用EventLog,在这里对于Java侧的SystemUI中EventLog为例进行剖析,对于C/C++层的EventLog留待后续分析.
先来看frameworks/base/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags的部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# See system/core/logcat/event.logtags for a description of the format of this file.

option java_package com.android.systemui;

# ---------------------------
# PhoneStatusBar.java
# ---------------------------
36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(disable1|1),(disable2|1)
36001 sysui_heads_up_status (key|3),(visible|1)
36002 sysui_fullscreen_notification (key|3)
36003 sysui_heads_up_escalation (key|3)
# sysui_status_bar_state: Logged whenever the status bar / keyguard state changes
## state: 0: SHADE, 1: KEYGUARD, 2: SHADE_LOCKED
## keyguardShowing: 1: Keyguard shown to the user (or keyguardOccluded)
## keyguardOccluded: 1: Keyguard active, but another activity is occluding it
## bouncerShowing: 1: Bouncer currently shown to the user
## secure: 1: The user has set up a secure unlock method (PIN, password, etc.)
## currentlyInsecure: 1: No secure unlock method set up (!secure), or trusted environment (TrustManager)
36004 sysui_status_bar_state (state|1),(keyguardShowing|1),(keyguardOccluded|1),(bouncerShowing|1),(secure|1),(currentlyInsecure|1)

从第一行我们看到在system/core/logcat/event.logtags中有EventLog格式的描述,我们到system/core/logcat/event.logtags看看,event.logtags自身也是一个EventLog文件,但是在此文件的开头和结尾,有对logtags格式的文件格式进行了描述,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# The entries in this file map a sparse set of log tag numbers to tag names.
# This is installed on the device, in /system/etc, and parsed by logcat.
# 此文件最终编译后位于system/etc/event-log-tags文件中,最终由logcat进行解析
#
# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the
# negative values alone for now.)
# Tag编号为十进制,从0到2^31
#
# Tag names are one or more ASCII letters and numbers or underscores, i.e.
# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former
# impacts log readability, the latter makes regex searches more annoying).
# Tag名字由字符串和数字组成,为了方便在log中搜索,name中避免使用空格和标点
#
# Tag numbers and names are separated by whitespace. Blank lines and lines
# starting with '#' are ignored.
# Tag的编号和名字由一个空格隔开,空行或者以#开头的行将被忽略
#
# Optionally, after the tag names can be put a description for the value(s)
# of the tag. Description are in the format
# (<name>|data type[|data unit])
# Multiple values are separated by commas.
# 根据需要,tag names后面可以加上这个tag的values来描述这个tag的打印格式。
#
# 数据类型由下列的数字组成:(如果数字类型时int,则data type用1表示)
# The data type is a number from the following values:
# 1: int
# 2: long
# 3: string
# 4: list
# 5: float
#
# data unit 表示数据格式,相当于data的单位:(1表示Number of objects)
# The data unit is a number taken from the following list:
# 1: Number of objects 对象个数
# 2: Number of bytes 对象字节数
# 3: Number of milliseconds 毫秒
# 4: Number of allocations 分配的个数
# 5: Id
# 6: Percent
# s: Number of seconds (monotonic time)
# Default value for data of type int/long is 2 (bytes).
#
# TODO: generate ".java" and ".h" files with integer constants from this file.

# NOTE - the range 1000000-2000000 is reserved for partners and others who
# want to define their own log tags without conflicting with the core platform.
# 1000000-2000000的tag number是留给合作厂商或者其他开发者用户扩展用的。

根据system/core/logcat/event.logtags中对logtags格式文件的描述,我们取上面的SystemUI中的logtags文件中的一个Event事件为例来说明:

1
36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(disable1|1),(disable2|1)

对应定义

1
#    (<name>|data type[|data unit])

则:

1
2
3
4
5
6
7
Tag Number: 36000
Tag name: sysui_statusbar_touch
value 1: name="type", data_type=1->int
value 2: name="x", data_type=1->int
value 3: name="y", data_type=1->int
value 4: name="disable1", data_type=1->int
value 5: name="disable2", data_type=1->int

接下来看看如何在SystemUI的Java代码中使用,在
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java中

1
2
3
4
5
6
7
8
9
10
public boolean interceptTouchEvent(MotionEvent event) {
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
event.getActionMasked(), (int) event.getX(), (int) event.getY(),
mDisabled1, mDisabled2);
}

}
...

可以看到在StatusBar的interceptTouchEvent方法中,将touch事件写入了event log中.

相关功能模块代码

本节将从EventLog调用流程来分析相关的关键代码块:

framework模块:

• frameworks/base/core/java/android/util/EventLogTags.java //已经被弃用,8.1.0中使用的是EventLog.java
• frameworks/base/core/java/android/util/EventLog.java
主要在EventLog.java文件中实现了下列函数供Java侧的各个模块调用:
a)实现writeEvent函数,通过JNI,调用到底层的代码,最终将event写入到/dev/log/events,调用关系如下:

1
2
3
4
EventLog.writeEvent() [android.util.EventLog][EventLog.java]
android_util_EventLog_writeEvent_Integer() [libandroid_runtime.so][android_util_EventLog.cpp]
android_bWriteLog() -> __android_log_bwrite() [liblog.so][]
write_to_log(LOG_ID_EVENTS, vec, 2) -> __write_to_log_kernel() [liblog.so]

b)实现readEvents函数,通过JNI,调用到底层代码, 具体使用场景还不没找,提供了方法来读取对应type的eventlog
c)实现getTagName函数,通过给出的ID,返回Tag的名称,主要通过在system/etc/event-log-tags文件里面进行查找实现,供Java侧其他模块调用
d)实现getTagCode函数,通过给出的Tag名称,返回Tag的ID,主要通过在system/etc/event-log-tags文件里面进行查找实现,供Java侧其他模块调用
e)实现内部静态类Event,这个是8.1.0之前的老版本的EventLog的实现.暂不关注.

JNI 模块:

• frameworks/base/core/jni/android_util_EventLog.cpp
实现了EventLog.java中的writeEvent和readEvent函数.在writeEvent函数中调用android_log_event_list类的write方法.

1
2
3
4
5
6
7
8
9
10
11
12
/*
* In class android.util.EventLog:
* static native int writeEvent(int tag, int value)
*/
static jint android_util_EventLog_writeEvent_Integer(JNIEnv* env UNUSED,
jobject clazz UNUSED,
jint tag, jint value)
{
android_log_event_list ctx(tag);
ctx << (int32_t)value;
return ctx.write();
}

framework模块的代码还有:
frameworks/base/core/java/com/android/internal/logging/MetricsLogger.java
frameworks/base/core/java/android/metrics/MetricsReader.java
以sysui_multi_action事件分析为例

liblog模块:

• system/core/liblog/include/log/log_event_list.h //定义了android_log_event_list类
• system/core/liblog/log_event_list.c //实现了android_log_event_list类的相关方法
• system/core/liblog/event_tag_map.cpp
• system/core/liblog/logger_write.c
liblog模块,在log_event_list.h中android_log_event_list.h中,初始化后,被<<赋值,执行android_log_write_int32函数,android_log_write_int32的实现在log_event_list.c,接着执行ctx.write().

1
2
3
4
5
6
7
8
int write(log_id_t id = LOG_ID_EVENTS) {
/* facilitate -EBUSY retry */
if ((ret == -EBUSY) || (ret > 0)) ret = 0;
int retval = android_log_write_list(ctx, id);
/* existing errors trump transmission errors */
if (!ret) ret = retval;
return ret;
}

在log_event_list.c 中执行android_log_write_list函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* Logs the list of elements to the event log.
*/
LIBLOG_ABI_PUBLIC int android_log_write_list(android_log_context ctx,
log_id_t id) {
android_log_context_internal* context;
const char* msg;
ssize_t len;

if ((id != LOG_ID_EVENTS) && (id != LOG_ID_SECURITY)) {
return -EINVAL;
}

context = (android_log_context_internal*)ctx;
if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
return -EBADF;
}
if (context->list_nest_depth) {
return -EIO;
}
/* NB: if there was overflow, then log is truncated. Nothing reported */
context->storage[1] = context->count[0];
len = context->len = context->pos;
msg = (const char*)context->storage;
/* it's not a list */
if (context->count[0] <= 1) {
len -= sizeof(uint8_t) + sizeof(uint8_t);
if (len < 0) {
len = 0;
}
msg += sizeof(uint8_t) + sizeof(uint8_t);
}
return (id == LOG_ID_EVENTS)
? __android_log_bwrite(context->tag, msg, len)
: __android_log_security_bwrite(context->tag, msg, len);
}

最后执行__android_log_bwrite函数,在logger_write.c中__android_log_bwrite函数如下:

1
2
3
4
5
6
7
8
9
10
11
LIBLOG_ABI_PUBLIC int __android_log_bwrite(int32_t tag, const void* payload,
size_t len) {
struct iovec vec[2];

vec[0].iov_base = &tag;
vec[0].iov_len = sizeof(tag);
vec[1].iov_base = (void*)payload;
vec[1].iov_len = len;

return write_to_log(LOG_ID_EVENTS, vec, 2);
}

write_to_log将此event事件写入dev/log/event节点中.
可以继续追踪write_to_log函数,在logger_write.c中

1
2
3
static int __write_to_log_init(log_id_t, struct iovec* vec, size_t nr);
static int (*write_to_log)(log_id_t, struct iovec* vec,
size_t nr) = __write_to_log_init;

则看__write_to_log_init函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
__android_log_lock();

if (write_to_log == __write_to_log_init) {
int ret;

ret = __write_to_log_initialize();
if (ret < 0) {
__android_log_unlock();
if (!list_empty(&__android_log_persist_write)) {
__write_to_log_daemon(log_id, vec, nr);
}
return ret;
}

write_to_log = __write_to_log_daemon;
}

__android_log_unlock();

return write_to_log(log_id, vec, nr);
}

追的有点远了,打住,回到eventlog的正题.

从上面的流程来看,在Java代码中相应函数内对EventLog进行打点,最后通过JNI回调和传参,就可以将Eventlog写入/dev/log/event节点了.//等等,8.1.0好像不是写入这个节点…
在这个过程中,我们用到了一类文件.logtags,从最还是我们看到了,这类文件主要用来用固定的格式来定义个EventLog事件,然后在Java文件中我们就可以直接使用定义好的事件名称和函数个数来打点Event事件.至于Java侧如何能够成功的调用Eventlog,大体流程是,Android代码在编译的过程中,build模块的一些脚本会自动的将相应模块的.logtags文件中的Event事件收集汇总到system/etc/event-log-tags文件中,同时也会将对应的*.logtags文件转换成一个EventLogTags.java文件看下面的分析

system中logd模块:

• system/core/logd/LogTags.cpp
• system/core/logd/LogTags.h
读取system/etc/event-log-tags文件

build模块:

• build/tools/java-event-log-tags.py //将对应的*.logtags文件转换为java文件
• build/tools/event_log_tags.py //输出event-log-tags文件
• build/tools/merge-event-log-tags.py //将生成的多个event-log-tags文件,整合到一个event-log-tags文件中
build模块中的脚本,从代码来看干了两件事情:
a)负责将各个模块中的EventLogTags.logtags文件转化为相对应的java文件,并输出到out/target/common/obj/JAVA_LIBRARIES 对应的目录中.
b)负责将各个模块中的*.logtags文件中的事件,进行收集,并输出到out/…/system/etc/event-log-tags文件中

对于生成EventLogTags.java文件,我们还是以上面的SystemUI模块为例,SystemUI模块中有一个EventLogTags.logtags文件
frameworks/base/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags,我们在单编完SystemUI之后,到out/target/common/obj/JAVA_LIBRARIES目录查找systemui相关的lib库文件,找到
out/target/common/obj/JAVA_LIBRARIES/SystemUI-tags_intermediates/src/src/com/android/systemui/EventLogTags.java
下面我们从此文件中抽出我们上面以SystemUI为例时的sysui_statusbar_touch事件在此java文件中的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* This file is auto-generated.  DO NOT MODIFY.
* Source file: frameworks/base/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
*/

package com.android.systemui;;

/**
* @hide
*/
public class EventLogTags {
private EventLogTags() { } // don't instantiate
...
/** 36010 sysui_panelbar_touch (type|1),(x|1),(y|1),(enabled|1) */
public static final int SYSUI_PANELBAR_TOUCH = 36010;
...
public static void writeSysuiPanelbarTouch(int type, int x, int y, int enabled) {
android.util.EventLog.writeEvent(SYSUI_PANELBAR_TOUCH, type, x, y, enabled);
}
...

可以看出多了一个静态常量和一个API可以调用,所以在源码里面可以这样调用(android源码的做法):
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
sr.crashCount, sr.shortName, app.pid);
这里只是用了AM_SERVICE_CRASHED_TOO_MUCH这个常量而没有调专用API,或者这样用(测试可以编译通过):
EventLog.writeAmServiceCrashedTooMuch(sr.crashCount, sr.shortName, app.pid);

EventLog在解Bug中的应用

下面列举tag可能使用的部分场景:
am_low_memory:位于AMS.killAllBackgroundProcesses或者AMS.appDiedLocked,记录当前Lru进程队列长度。
am_pss:位于AMS.recordPssSampleLocked(
am_meminfo:位于AMS.dumpApplicationMemoryUsage
am_proc_start:位于AMS.startProcessLocked,启动进程
am_proc_bound:位于AMS.attachApplicationLocked
am_kill: 位于ProcessRecord.kill,杀掉进程
am_anr: 位于AMS.appNotResponding
am_crash:位于AMS.handleApplicationCrashInner
am_wtf:位于AMS.handleApplicationWtf
am_activity_launch_time:位于ActivityRecord.reportLaunchTimeLocked(),后面两个参数分别是thisTime和 totalTime.
am_activity_fully_drawn_time:位于ActivityRecord.reportFullyDrawnLocked, 后面两个参数分别是thisTime和 totalTime
am_broadcast_discard_filter:位于BroadcastQueue.logBroadcastReceiverDiscardLocked
am_broadcast_discard_app:位于BroadcastQueue.logBroadcastReceiverDiscardLocked

Activity生命周期相关的方法:
am_on_resume_called: 位于AT.performResumeActivity
am_on_paused_called: 位于AT.performPauseActivity, performDestroyActivity
am_resume_activity: 位于AS.resumeTopActivityInnerLocked
am_pause_activity: 位于AS.startPausingLocked
am_finish_activity: 位于AS.finishActivityLocked, removeHistoryRecordsForAppLocked
am_destroy_activity: 位于AS.destroyActivityLocked
am_focused_activity: 位于AMS.setFocusedActivityLocked, clearFocusedActivity
am_restart_activity: 位于ASS.realStartActivityLocked
am_create_activity: 位于ASS.startActivityUncheckedLocked
am_new_intent: 位于ASS.startActivityUncheckedLocked
am_task_to_front: 位于AS.moveTaskToFrontLocked

下面列举tag可能使用的部分场景:
power_sleep_requested: 位于PMS.goToSleepNoUpdateLocked
power_screen_state:位于Notifer.handleEarlyInteractiveChange, handleLateInteractiveChange

battery_level: [19,3660,352] //剩余电量19%, 电池电压3.66v, 电池温度35.2℃
power_screen_state: [0,3,0,0] // 灭屏状态(0), 屏幕超时(3). 当然还有其他设备管理策略(1),其他理由都为用户行为(2)
power_screen_state: [1,0,0,0] // 亮屏状态(1)

补充:
am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
am_proc_start:[0,9227,10002,com.android.browser,contentprovider,com.android.browser/.provider.BrowserProvider2]
–> (User|1|5) ==> 名字为User, 数据类型为1,数据单位为5)
数据类型:1: int、2: long、3: string、4: list
数据单位:1: Number of objects(对象个数)、2: Number of bytes(字节数)、3: Number of milliseconds(毫秒)、4: Number of allocations(分配个数)、5: Id、6: Percent(百分比)
–> 进程启动: UserId=0, pid=9227, uid=10002, ProcessName=com.android.browser, 数据类型=ContentProvider, 组件=com.android.browser/.provider.BrowserProvider2

event log使用小技巧

• 通过Tag ID来过滤event log
adb logcat -t 524292 -b events //打印出所有sysui_multi_action事件的event log
adb logcat -v threadtime -t 524292 -b events
• 通过Tag name来过滤event log
adb logcat -b events | grep -rn sysui_multi_action //打印出所有sysui_multi_action事件的event log
adb logcat -v threadtime -b events | grep -rn sysui_multi_action

在Android中所有已经定义了的Eventlog事件可以在这里查看
Eventlog相关的文件可以到这里查看

如下待完成补充:
C/C++模块中EventLog的使用,
• system/core/logcat/event.logtags

思考:Event log的优势在哪里?其实Event log要实现的功能,main log其实也是可以实现的.
我的回答:对不同log划分了不同缓冲区后,就不用担心log被覆盖掉,关键的event log不用担心main太多被冲掉.

参考阅读:
Android event日志打印原理
【framework】EventLog分析

Android中其他log分析

LINUX KERNEL源代码目录结构

/arch:硬件体系结构相关的代码,每种平台占一个相应的目录
/drivers:设备驱动程序,每个不同的驱动占用一个子目录
/fs:文件系统,包含所有的文件系统代码和各种类型的文件操作代码,它的每一个子目录支持一个文件系统
/include:包括编译核心所需要的大部分头文件。与平台无关的头文件在 include/linux 子目录下,与intel cpu 相关的头文件在 include/asm-i386
子目录下,而 include/scsi 目录则是有关scsi 设备的头文件目录
init:这个目录包含核心的初始化代码(不是系统的引导代码),包含两个文件 main.c 和 Version.c,这是研究核心如何工作的好起点之一
/ipc:核心的进程间通讯的代码
/kernel:主要的核心代码,此目录下的文件是内核的最核心部分,包括进程调度、定时器等,实现了大多数Linux 系统的内核函数,其中重要的
文件尾 sched.c ;同样,和体系结构相关的代码在 arch/*/kernel 中
/lib:放置核心库代码
/mm:这个目录包括所有独立于 CPU 体系机构的内存管理代码,如页式存储管理内存的分配和释放等;而合体系结构相关的内存管理代码则
位于 arch/*/mm
/net:网络相关代码,实现了各种常见的网络协议
/scripts:描述文件,脚本,用于对核心的配置
/security:主要是一个SELinux 的模块
/sound:常用音频设备的驱动程序等
/usr:实现了一个 cpio
/block:部分块设备驱动程序
/crypto:常用加密和散列算法(如AES、SHA等),以及一些压缩和CRC 校验算法
/Documentation:一套关于内核部分的调用解释和注释用的英文文档

根目录下几个文件:
COPYING:GPL 版权申明,对具有GPL 版权的源代码改动而形成的程序,具有使用GPL 发表的义务,如公开源代码
CREDITS:光荣榜。对Linux 做出过很大贡献的一些人的信息
Kbuild:是编译内核的软件环境,它泛指构建一个完全并能够运行Linux 内核所需要的一切资源。这些资源包含构建程序、脚本、中间件、配置
文件和Makefile
MAINTAINERS:维护人员列表,对当前版本的内核部分都有谁负责
Makefile:第一个Makefile 文件。用来组织内核的各模块,记录了各模块相互之间的联系和依托关系,编译时使用;仔细阅读各子目录下的
Makefile文件对弄清各个文件之间的联系和依托关系很有帮助
ReadMe:核心以及编译配置方法简单介绍取模
REPORING-BUGS:有关报告 Bug 的一些内容

Android 解读main log和event log日志信息
https://blog.csdn.net/yelangjueqi/article/details/52621903

Log中’main’, ‘system’, ‘radio’, ‘events’以及android log分析
https://www.cnblogs.com/zhengtu2015/p/5134012.html

关于Android Log的一些思考
https://droidyue.com/blog/2015/11/01/thinking-about-android-log/

https://blog.csdn.net/fishmai/article/details/52398537

crash/WTF/ANR/Low on memory —dropbox

手机的默认的日志目录:

/data/local/tmp/*

/data/tmp/*

/data/system/usagestats/*

/data/system/appusagestates/*

/data/system/dropbox/*

/data/tombstones/*

/data/anr/*

logcat的日志在

/dev/log/main

有/data/local/log目录的,可以保存3-4天的log。

需要对Android的log机制有深入的研究

logd源码分析

logd源码位置:system/core/logd

logwraper 源码分析
logwraper源码位置:system/core/logwrapper/logwrap.c

参考资料

那两年炼就的Android内功修养
浅谈Android系统开发中LOG的使用
Android日志系统驱动程序Logger源代码分析
Android应用程序框架层和系统运行库层日志系统源代码分析
Android日志系统Logcat源代码简要分析

misc设备 杂项设备
[mutex互斥锁]
Android L日志系统2——JavaAPI与liblog
Android6的Logger日志系统
Android6.0 Log的工作机制
Android O 配置logcatd日志系统