提供了一些格式化输出、美观
// 添加依赖
implementation 'com.orhanobut:logger:2.2.0'
// 初始化
Logger.addLogAdapter(new AndroidLogAdapter());
// 使用
Logger.d("hello,Android");
基于原生Log
类的小型可扩展的log框架
使用注解形式的调试版本log框架
可扩展,支持多种数据格式,支持线程和调用栈信息
支持多种数据结构,支持系统对象,支持高性能写入文件(mmap)
基于mmap内存映射,最大化保证日志完整性
Android 系统 Java 层 Log 定义在 /frameworks/base/core/java/android/util/Log.java
此外还提供了 EventLog 和 SLog【todo】
Log 提供了六种日志级别,并定义了一系列静态方法:
public static int v/d/i/w/e/wtf(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int v/d/i/w/e/wtf(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
下面这种调用带异常记录,会调用 Log 类内部类 ImediateLogWriter
来写入日志消息,最终也会调用 println_native
。
说明:Android 中不同的 log 会指定不同的缓冲区然后被写入到不同的设备中,包括 system(系统相关)、radio(无线/电话相关)、event(事件相关)、main(主缓冲区,默认)
本地实现定义在 /frameworks/base/core/jni/android_util_Log.cpp
static const JNINativeMethod gMethods[] = {
{ "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 },
};
android_util_Log_isLoggable
isLoggable
判断
__android_log_is_loggable
获取 logLevel【/system/core/liblog/properties.c,编译成 liblog.so】android_util_Log_println_native
__android_log_buf_write
【/system/core/liblog/logger_write.c,编译到 liblog.so】Native 层通过定义一系列宏的方式提供 log 功能,全部是调用了 __android_log_print
LIBLOG_ABI_PUBLIC int __android_log_print(int prio, const char* tag,
const char* fmt, ...) {
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_write(prio, tag, buf);
}
这个过程使用了可变参数,va 就是 variable-argument,相关宏定义在 stdarg.h 中
最后函数会调用 __android_log_write
LIBLOG_ABI_PUBLIC int __android_log_write(int prio, const char* tag,
const char* msg) {
return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}
这里 Java/C++ 层就走到同一个函数,在这个函数中会实现写设备文件
liblog.so 会被所有需要日志操作的进程加载,负责处理打印和读取日志的流程
主要代码及逻辑都在 /system/core/liblog/logger_write.c 中,__android_log_buf_write
里面做了下面这几件事:
定义 iovec 结构数组 vec[3],三个元素分别存放 prio、tag、msg
struct iovec {
void* iov_base;
size_t iov_len;
};
iovec 结构体包含一个指向缓冲区的指针和读/写的长度
判断 bufID 修改 tag(bufID 这个变量表示不同的 log 缓冲区,或者理解成写到不同的文件里)
最后调用 write_to_log
write_to_log
是一个函数指针,初始设置指向 __write_to_log_init
,进入 __write_to_log_init
后,首先会去调用 __write_to_log_initialize
,然后将 write_to_log
设置为指向 __write_to_log_daemon
,然后又调用一次 write_to_log
static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;
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);
}
__write_to_log_initialize
和 __write_to_log_daemon
实现太复杂了,总结下来就是 initialize 基于默认配置构造结构体链表,daemon 从链表中取出节点,就是 android_log_transport_write
,节点的结构体的定义在liblog/logger.h 中
struct android_log_transport_write {
struct listnode node;
const char* name; /* human name to describe the transport */
unsigned logMask; /* mask cache of available() success */
union android_log_context context; /* Initialized by static allocation */
int (*available)(log_id_t logId); /* Does not cause resources to be taken */
int (*open)(); /* can be called multiple times, reusing current resources */
void (*close)(); /* free up resources */
int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr); /* write log to transport, returns number of bytes propagated, or -errno */
};
初始化 的时候会调用 __android_log_config_write
会基于不同的场景定义不同的结构去写日志,包括 localLoggerWrite、logdLoggerWrite、pmsgLoggerWrite、fakeLoggerWrite、stderrLoggerWrite,调用 retval = (*node->write)(log_id, &ts, vec, nr) 进行 write 操作
LIBLOG_HIDDEN void __android_log_config_write() {
if (__android_log_transport & LOGGER_LOCAL) {
extern struct android_log_transport_write localLoggerWrite;
__android_log_add_transport(&__android_log_transport_write,
&localLoggerWrite);
}
if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
#if (FAKE_LOG_DEVICE == 0)
extern struct android_log_transport_write logdLoggerWrite;
extern struct android_log_transport_write pmsgLoggerWrite;
__android_log_add_transport(&__android_log_transport_write, &logdLoggerWrite);
__android_log_add_transport(&__android_log_persist_write, &pmsgLoggerWrite);
#else
extern struct android_log_transport_write fakeLoggerWrite;
__android_log_add_transport(&__android_log_transport_write, &fakeLoggerWrite);
#endif
}
if (__android_log_transport & LOGGER_STDERR) {
extern struct android_log_transport_write stderrLoggerWrite;
if (list_empty(&__android_log_transport_write)) {
__android_log_add_transport(&__android_log_transport_write, &stderrLoggerWrite);
} else {
struct android_log_transport_write* transp;
write_transport_for_each(transp, &__android_log_transport_write) {
if (transp == &stderrLoggerWrite) {
return;
}
}
__android_log_add_transport(&__android_log_persist_write, &stderrLoggerWrite);
}
}
}
看其中比较重要的几个,logdLoggerWrite
的定义在 /system/core/liblog/logd_writer.c
LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = {
.node = { &logdLoggerWrite.node, &logdLoggerWrite.node },
.context.sock = -EBADF,
.name = "logd",
.available = logdAvailable,
.open = logdOpen,
.close = logdClose,
.write = logdWrite,
};
其中,logdOpen 方法创建 sockaddr_un 结构体并将 “/dev/socket/logdw” 写入 sun_path 成员变量中,然后调用 connect 去建立连接,并将套接字标识放到 logdLoggerWrite.context.sock 中;write 函数指针指向 logdWrite
方法,会调用 writev
非阻塞地写入日志信息
同理,pmsgLoggerWrite 打开的是 “/dev/pmsg0” 的 socket
所以,liblog.so 的一个主要工作就是写入日志
使用 file 命令查看 /dev/socket/logdw 发现是一个 socket,这个 socket 是由 logd 创建的,见 /system/corelogd/logd.rc
service logd /system/bin/logd
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram+passcred 0222 logd logd
file /proc/kmsg r
file /dev/kmsg w
user logd
group logd system package_info readproc
writepid /dev/cpuset/system-background/tasks
logd 是 C 层的守护进程,由 init 进程创建(创建 servicemanager 时同时创建),可以看到启动 logd 后会创建三个 socket,分别为 logd 用来监听命令、logdr 用于读日志、logdw 用于写日志,还打开了两个文件,修改了 user id 和 group id,并把 pid 写文件
logd 启动后会从 main 函数开始执行,见 /system/core/logd/main.cpp
第一步、打开 /dev/kmsg 用于写内核 log 的,在 logd 还未启动或出错时,只能写到内核日志中
int main(int argc, char* argv[]) {
......
static const char dev_kmsg[] = "/dev/kmsg";
fdDmesg = android_get_control_file(dev_kmsg);
if (fdDmesg < 0) {
fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
......
}
android_get_control_file 里面先调用 __android_get_control_from_env
拼接 Android 文件前缀 ANDROID_FILE_ 和路径 /dev/kmsg,然后做符号转换得到 ANDROID_FILE__dev_kmsg
,接下来通过 getenv 获取这个环境变量的值,最后通过 strtol 将这个值转换成 long 类型就是文件描述符,还要通过 fcntl 去验证下文件是不是开着
这里用了三种方法去验证:
__android_get_control_from_env
返回文件描述符后,还会调用 /proc/self/fd/fd_num 验证一次
第二步、打开 /proc/kmsg 用于读内核日志
int main(int argc, char* argv[]) {
......
bool klogd = __android_logger_property_get_bool(
"logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
if (klogd) {
static const char proc_kmsg[] = "/proc/kmsg";
fdPmesg = android_get_control_file(proc_kmsg);
if (fdPmesg < 0) {
fdPmesg = TEMP_FAILURE_RETRY(
open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
}
if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
}
......
}
第三步、启动 reinit 线程,处理 --reinit 命令,此外这个线程还会完成 uid 转 name
第四步、设置运行时优先级和权限【略】
第五步、启动 log 监听
看一下 logd 的线程,可以看到 logd 进程 pid = 574,ppid = 1,即 init 进程孵化,reinit_thread_start
函数启动线程“logd.daemon”,LogReader 启动线程“logd.reader”监听 /dev/socket/logdr,LogListener 启动线程“logd.writer”监听 /dev/socket/logdw,CommandListener 启动线程“logd.control”监听 /dev/socket/logd,LogAudit 启动线程“logd.auditd”,LogKlog 启动线程“logd.klogd”,LogTimeEntry 启动线程“logd.reader.per”
logd 574 574 1 32428 4912 SyS_rt_sigsuspend 753d7b1634 S logd
logd 574 577 1 32428 4912 futex_wait_queue_me 753d7644b0 S logd.daemon
logd 574 578 1 32428 4912 do_select 753d7b15a4 S logd.reader
logd 574 579 1 32428 4912 do_select 753d7b15a4 S logd.writer
logd 574 580 1 32428 4912 do_select 753d7b15a4 S logd.control
logd 574 582 1 32428 4912 do_select 753d7b15a4 S logd.auditd
logd 574 14402 1 32428 4912 futex_wait_queue_me 753d7644b0 S logd.reader.per
LogBuffer 继承自 LogBufferInterface,类内部定义了很多成员变量和一些函数,包括:
LogBufferElementCollection 是一个 LogBufferElement 指针类型的 list
LogBufferElement 中存放 LogBuffer 元素的相关信息,包括 uint32_t 类型的 uid/pid/tid,还有 realTime、消息内容 msg,logId 等;
LastLogTimes 是一个 LogTimeEntry 指针类型的 list,两者都定义在 LogTime 中,LogTimeEntry 里面包含很多线程相关的变量及方法,还有 SocketClient 对象,表示读取日志的客户端
LogBuffer 的构造过程如下,传入参数为 LastLogTimes指针
LogBuffer* logBuf = nullptr;
LastLogTimes* times = new LastLogTimes();
logBuf = new LogBuffer(times);
继续看 LogBuffer 的构造函数
LogBuffer::LogBuffer(LastLogTimes* times)
: monotonic(android_log_clockid() == CLOCK_MONOTONIC), mTimes(*times) {
pthread_rwlock_init(&mLogElementsLock, nullptr);
log_id_for_each(i) {
lastLoggedElements[i] = nullptr;
droppedElements[i] = nullptr;
}
init();
}
monotonic 表示时间格式,即 CPU 通电时间或实际时间,mLogElementsLock 为读写锁,log_id_for_each 会通过 ID 遍历所有日志初始化 lastLoggedElements 和 droppedElements,最后调用 init 进行初始化
void LogBuffer::init() {
......
LogTimeEntry::wrlock();
LastLogTimes::iterator times = mTimes.begin();
while (times != mTimes.end()) {
LogTimeEntry* entry = (*times);
if (entry->owned_Locked()) {
entry->triggerReader_Locked();
}
times++;
}
LogTimeEntry::unlock();
}
init 函数中会先遍历 logID 设置日志最大容量,然后检查时间格式并作类型转换,最后依次取出 LastLogTimes 中的元素,即一个 LogTimeEntry 对象,调用 triggerReader_Locked
,这个方法的作用是,发送条件变量去唤醒另一个处于阻塞状态的线程,辅助后续的日志读写
LogListener 调用逻辑很简单,就是初始化一个监听对象,然后开启监听
LogListener* swl = new LogListener(logBuf, reader);
if (swl->startListener(600)) {
exit(1);
}
LogListener 继承自 SocketListener,这个是 sysutils 库提供的类,用于监听客户端的 socket 连接,包含两个成员变量 LogBufferInterface 和 LogReader,也是构造函数的参数,还有一个 onDataAvailable
的回调,以及一个获取 getLogSocket 函数。
class LogListener : public SocketListener {
LogBufferInterface* logbuf;
LogReader* reader;
public:
LogListener(LogBufferInterface* buf, LogReader* reader /* nullable */);
protected:
virtual bool onDataAvailable(SocketClient* cli);
private:
static int getLogSocket();
};
getLogSocket 会构造路径 /dev/socket/logdw 作为 socketName,然后调用 socket_local_server 启动一个 socket 接收客户端连接
int LogListener::getLogSocket() {
static const char socketName[] = "logdw";
int sock = android_get_control_socket(socketName);
if (sock < 0) { // logd started up in init.sh
sock = socket_local_server(
socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);
int on = 1;
if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
return -1;
}
}
return sock;
}
再看 LogListener 构造函数
LogListener::LogListener(LogBufferInterface* buf, LogReader* reader)
: SocketListener(getLogSocket(), false), logbuf(buf), reader(reader) {
}
会调用 SocketListener 构造函数,然后进一步调用 init 函数,里面只是做了变量赋值,然后初始化了一个 SocketClientCollection 类型指针
SocketListener::SocketListener(int socketFd, bool listen) {
init(NULL, socketFd, listen, false);
}
void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
mListen = listen;
mSocketName = socketName;
mSock = socketFd;
mUseCmdNum = useCmdNum;
pthread_mutex_init(&mClientsLock, NULL);
mClients = new SocketClientCollection();
}
SocketClientCollection 是 SocketClient 指针类型的 list ,两者都定义在 SocketClient 中,SocketClient 包含一些客户端 socket 连接的变量。
LogListener 构造完成后就会调用父类的 startListener
方法开始监听,根据参数主要调用了这几个方法
int SocketListener::startListener(int backlog) {
......
mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));
if (pipe(mCtrlPipe)) {
......
if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
......
}
首先会构造一个 SocketClient 对象,然后把这个对象放到 mClients 里面。
SocketClient 构造函数会调用 init
函数,里面会做一些变量赋值,注意这里调用 getsockopt
返回成功,但是这个 socket 还是服务器 socket,所以 creds 相关参数都是无效的
SocketClient::SocketClient(int socket, bool owned, bool useCmdNum) {
init(socket, owned, useCmdNum);
}
void SocketClient::init(int socket, bool owned, bool useCmdNum) {
mSocket = socket;
mSocketOwned = owned;
mUseCmdNum = useCmdNum;
pthread_mutex_init(&mWriteMutex, NULL);
pthread_mutex_init(&mRefCountMutex, NULL);
mPid = -1;
mUid = -1;
mGid = -1;
mRefCount = 1;
mCmdNum = 0;
struct ucred creds;
socklen_t szCreds = sizeof(creds);
memset(&creds, 0, szCreds);
int err = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
if (err == 0) {
mPid = creds.pid;
mUid = creds.uid;
mGid = creds.gid;
}
}
接着会创建一个 mCtrlPipe 的管道,用于唤醒 select
系统调用,最后创建一个线程开启监听
void *SocketListener::threadStart(void *obj) {
SocketListener *me = reinterpret_cast<SocketListener *>(obj);
me->runListener();
pthread_exit(NULL);
return NULL;
}
SocketListener::threadStart
里面会调用 SocketListener 的 runListener
这个函数会监听 mCtrlPipe,将套接口放到 read_fds 数组中,然后开启 select 进行监听。如果 mCtrlPipe 中写入了 CtrlPipe_Shutdown 则退出线程;如果 mSock 可监听且可读,则表示有客户端连接,使用 accept
接收客户端连接的 socket,使用此 socket 构造 SocketClient 并放到 mClients 中,最后依次处理 SocketClient 的 socket,即回调子类的 onDataAvailable
函数
fd_set 数据结构,为 long 类型的数组,存放一组等待检查的套接口,select 返回查询到的满足状态的套接口数目
void SocketListener::runListener() {
SocketClientCollection pendingList;
while(1) {
SocketClientCollection::iterator it;
fd_set read_fds;
FD_ZERO(&read_fds);
......
FD_SET(mCtrlPipe[0], &read_fds);
if (mCtrlPipe[0] > max)
max = mCtrlPipe[0];
pthread_mutex_lock(&mClientsLock);
for (it = mClients->begin(); it != mClients->end(); ++it) {
int fd = (*it)->getSocket();
FD_SET(fd, &read_fds);
if (fd > max) {
max = fd;
}
}
pthread_mutex_unlock(&mClientsLock);
SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);
if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
......
if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
char c = CtrlPipe_Shutdown;
TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
if (c == CtrlPipe_Shutdown) {
break;
}
continue;
}
if (mListen && FD_ISSET(mSock, &read_fds)) {
int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
......
pthread_mutex_lock(&mClientsLock);
mClients->push_back(new SocketClient(c, true, mUseCmdNum));
pthread_mutex_unlock(&mClientsLock);
}
pendingList.clear();
pthread_mutex_lock(&mClientsLock);
for (it = mClients->begin(); it != mClients->end(); ++it) {
SocketClient* c = *it;
int fd = c->getSocket();
if (FD_ISSET(fd, &read_fds)) {
pendingList.push_back(c);
c->incRef();
}
}
pthread_mutex_unlock(&mClientsLock);
while (!pendingList.empty()) {
it = pendingList.begin();
SocketClient* c = *it;
pendingList.erase(it);
if (!onDataAvailable(c)) {
release(c, false);
}
c->decRef();
}
}
}
LogListener 的 onDataAvailable
会完成对日志的读取操作。
bool LogListener::onDataAvailable(SocketClient* cli) {
static bool name_set;
if (!name_set) {
prctl(PR_SET_NAME, "logd.writer");
name_set = true;
}
......
ssize_t n = recvmsg(socket, &hdr, 0);
struct ucred* cred = NULL;
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
while (cmsg != NULL) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS) {
cred = (struct ucred*)CMSG_DATA(cmsg);
break;
}
cmsg = CMSG_NXTHDR(&hdr, cmsg);
}
......
if (logbuf != nullptr) {
int res = logbuf->log((log_id_t)header->id, header->realtime,
cred->uid, cred->pid, header->tid, msg,
((size_t)n <= USHRT_MAX) ? (unsigned short)n :
USHRT_MAX);
if (res > 0 && reader != nullptr) {
reader->notifyNewLog();
}
}
return true;
}
首先调用系统调用 prctl 设置线程名称为 logd.writer,,然后调用 recvmsg 从 socket 中读取日志数据到 msghdr 这么一个结构体中,接着调用 CMSG_FIRSTHDR 获取指向 cmsghdr 结构的辅助信息,进而拿到应用相关的信息放到 ucred 结构中,最后调用 logbuf->log 写入数据,如果发现 reader 不为空,即客户端在等待读取数据,则调用 notifyNewLog
通知有新日志。
msghdr 对应的内核结构为 user_msghdr,结构为
struct user_msghdr {
void __user *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iovec __user *msg_iov; /* scatter/gather array */
__kernel_size_t msg_iovlen; /* # elements in msg_iov */
void __user *msg_control; /* ancillary data */
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
};
继续看 LogBuffer::log
方法如何写入日志,先用传入的参数构造 LogBufferElement 对象,然后根据 prio 和 tag 判断日志是否可以写入
int LogBuffer::log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
pid_t tid, const char* msg, unsigned short len) {
......
LogBufferElement* elem =
new LogBufferElement(log_id, realtime, uid, pid, tid, msg, len);
if (log_id != LOG_ID_SECURITY) {
int prio = ANDROID_LOG_INFO;
const char* tag = nullptr;
if (log_id == LOG_ID_EVENTS) {
tag = tagToName(elem->getTag());
} else {
prio = *msg;
tag = msg + 1;
}
if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
// Log traffic received to total
wrlock();
stats.addTotal(elem);
unlock();
delete elem;
return -EACCES;
}
}
......
}
接下来使用一个状态机去除重复的日志,过程比较复杂没看懂先略过,最后会调另一个 log 方法去写日志
int LogBuffer::log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
pid_t tid, const char* msg, unsigned short len) {
......
wrlock();
......
log(elem);
unlock();
return len;
}
这个 log 方法传入的对象就是我们上面构造好的那个 LogBufferElement 对象,大致逻辑就是把这个构造好的 LogBufferElement 插入到 mLogElements 列表正确的位置
小结:
(logd)main.cpp
LogReader 调用跟 LogListener 类似,也是初始化一个监听对象然后开启监听
LogReader* reader = new LogReader(logBuf);
if (reader->startListener()) {
exit(1);
}
LogReader 也是继承自 SocketListener,成员变量为 LogBuffer,也是构造函数的参数,还有一个 onDataAvailable
的回调,一个获取 getLogSocket
函数,以及一些其他的函数
class LogReader : public SocketListener {
LogBuffer& mLogbuf;
public:
explicit LogReader(LogBuffer* logbuf);
void notifyNewLog();
LogBuffer& logbuf(void) const {
return mLogbuf;
}
protected:
virtual bool onDataAvailable(SocketClient* cli);
private:
static int getLogSocket();
void doSocketDelete(SocketClient* cli);
};
这里的 getLogSocket
会构造路径 /dev/socket/logdr 作为 socketName,然后调用 socket_local_server
启动一个 socket 接收客户端连接
int LogReader::getLogSocket() {
static const char socketName[] = "logdr";
int sock = android_get_control_socket(socketName);
if (sock < 0) {
sock = socket_local_server(
socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET);
}
return sock;
}
LogReader 构造函数会调用 SocketListener
构造函数,然后进一步调用 init
函数进行初始化,构造完成后就会调用父类的 startListener
方法开始监听,接着就会回调子类的 onDataAvailable
bool LogReader::onDataAvailable(SocketClient* cli) {
static bool name_set;
if (!name_set) {
prctl(PR_SET_NAME, "logd.reader");
name_set = true;
}
......
int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
......
FlushCommand command(*this, nonBlock, tail, logMask, pid, sequence, timeout);
// Set acceptable upper limit to wait for slow reader processing b/27242723
struct timeval t = { LOGD_SNDTIMEO, 0 };
setsockopt(cli->getSocket(), SOL_SOCKET, SO_SNDTIMEO, (const char*)&t,
sizeof(t));
command.runSocketCommand(cli);
return true;
}
首先调用系统调用 prctl 设置线程名称为 logd.reader,然后调用 read
读取 socket 并提取 " tail="、" start=" 等相关字符串来设置 tail、start、timeout、logMask、pid、nonBlock 这些参数,最后会利用这些参数构造一个 FlushCommand 对象,并调用 runSocketCommand
方法来真正读取日志
FlushCommand 继承自 SocketClientCommand,构造函数就是初始化参数
class FlushCommand : public SocketClientCommand {
LogReader& mReader;
bool mNonBlock;
unsigned long mTail;
unsigned int mLogMask;
pid_t mPid;
log_time mStart;
uint64_t mTimeout;
public:
explicit FlushCommand(LogReader& mReader, bool nonBlock = false,
unsigned long tail = -1, unsigned int logMask = -1,
pid_t pid = 0, log_time start = log_time::EPOCH,
uint64_t timeout = 0);
virtual void runSocketCommand(SocketClient* client);
static bool hasReadLogs(SocketClient* client);
static bool hasSecurityLogs(SocketClient* client);
};
前面 LogListener 的 onDataAvailable
函数在对日志做读取操作时,判断 reader 不为空就会调用 reader->notifyNewLog()
来通知 LogReader 读取日志,
void LogReader::notifyNewLog() {
FlushCommand command(*this);
runOnEachSocket(&command);
}
这里 runOnEachSocket
由 SocketListener 实现,里面实际上就是会针对每一个 SocketClient 去执行 runSocketCommand
void SocketListener::runOnEachSocket(SocketClientCommand *command) {
SocketClientCollection safeList;
/* Add all active clients to the safe list first */
safeList.clear();
pthread_mutex_lock(&mClientsLock);
SocketClientCollection::iterator i;
for (i = mClients->begin(); i != mClients->end(); ++i) {
SocketClient* c = *i;
c->incRef();
safeList.push_back(c);
}
pthread_mutex_unlock(&mClientsLock);
while (!safeList.empty()) {
/* Pop the first item from the list */
i = safeList.begin();
SocketClient* c = *i;
safeList.erase(i);
command->runSocketCommand(c);
c->decRef();
}
}
继续看 FlushCommand::runSocketCommand
,会遍历 LastLogTimes 里面的元素找到自己这个 SocketClient,然后会唤醒线程读日志,没找到则构建一个 LogTimeEntry 然后等待被唤起,最后执行 entry->startReader_Locked()
启动线程读日志
void FlushCommand::runSocketCommand(SocketClient* client) {
LastLogTimes::iterator it = times.begin();
while (it != times.end()) {
entry = (*it);
if (entry->mClient == client) {
......
entry->triggerReader_Locked();
if (entry->runningReader_Locked()) {
LogTimeEntry::unlock();
return;
}
entry->incRef_Locked();
break;
}
it++;
}
if (it == times.end()) {
// Create LogTimeEntry in notifyNewLog() ?
if (mTail == (unsigned long)-1) {
LogTimeEntry::unlock();
return;
}
entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask,
mPid, mStart, mTimeout);
times.push_front(entry);
}
client->incRef();
// release client and entry reference counts once done
entry->startReader_Locked();
}
接着看 LogTimeEntry::startReader_Locked
,会调用 LogTimeEntry::threadStart
启动读日志线程
void LogTimeEntry::startReader_Locked(void) {
pthread_attr_t attr;
threadRunning = true;
if (!pthread_attr_init(&attr)) {
if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
if (!pthread_create(&mThread, &attr, LogTimeEntry::threadStart,
this)) {
pthread_attr_destroy(&attr);
return;
}
}
pthread_attr_destroy(&attr);
}
threadRunning = false;
if (mClient) {
mClient->decRef();
}
decRef_Locked();
}
设置线程名为 “logd.reader.per”,然后读取 mTail 条日志,这里有两次调用,第一次先遍历一遍,再实际读取日志,其中 FilterFirstPass 和 FilterSecondPass 参数为执行成功的回调函数,FilterFirstPass 计算元素的个数,FilterSecondPass 会返回指定的元素
void* LogTimeEntry::threadStart(void* obj) {
prctl(PR_SET_NAME, "logd.reader.per");
LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);
......
if (me->mTail) {
logbuf.flushTo(client, start, nullptr, privileged, security,
FilterFirstPass, me);
me->leadingDropped = true;
}
start = logbuf.flushTo(client, start, me->mLastTid, privileged,
security, FilterSecondPass, me);
......
}
return nullptr;
}
跟着看 LogBuffer::flushTo
,首先根据参数 start 找到开始迭代的地方,然后执行 FilterPass 回调得到指定的日志元素,最后调用 element->flushTo
将日志写入
log_time LogBuffer::flushTo(SocketClient* reader, const log_time& start,
pid_t* lastTid, bool privileged, bool security,
int (*filter)(const LogBufferElement* element,
void* arg),
void* arg) {
......
for (; it != mLogElements.end(); ++it) {
LogBufferElement* element = *it;
......
if (filter) {
int ret = (*filter)(element, arg);
......
curr = element->flushTo(reader, this, privileged, sameTid);
......
return curr;
}
最后看 LogBufferElement::flushTo
,构造 logger_entry_v4 结构体对象,然后放到 iovec 数组中,最后通过 reader->sendDatav
写入 socket
log_time LogBufferElement::flushTo(SocketClient* reader, LogBuffer* parent,
bool privileged, bool lastSame) {
struct logger_entry_v4 entry;
memset(&entry, 0, sizeof(struct logger_entry_v4));
struct iovec iovec[2];
iovec[0].iov_base = &entry;
iovec[0].iov_len = entry.hdr_size;
char* buffer = NULL;
if (mDropped) {
entry.len = populateDroppedMessage(buffer, parent, lastSame);
if (!entry.len) return mRealTime;
iovec[1].iov_base = buffer;
} else {
entry.len = mMsgLen;
iovec[1].iov_base = mMsg;
}
iovec[1].iov_len = entry.len;
log_time retval = reader->sendDatav(iovec, 1 + (entry.len != 0))
? FLUSH_ERROR
: mRealTime;
if (buffer) free(buffer);
return retval;
}
小结:
(logd)main.cpp
===========================================================
Java 层:`android.utils.Log` -> `println_native` ->
JNI 层:`android_util_Log_println_native` ->
-----------------------------------------------------------
C/C++ 层:`__android_log_print` -> `__android_log_write`
===========================================================
liblog.so: `__android_log_buf_write` -> `write_to_log`
-> `__write_to_log_init` -> `write_to_log`
-> `__write_to_log_daemon`
===========================================================
libd process:
LogBuffer:LogBufferElement、LogTimeEntry
LogListener:SocketListener、SocketClient
LogReader:SocketListener、FlushCommand
===========================================================
logcat 是 Android 提供的一个日志打印工具,主要包含打印、过滤、清除日志等功能,运行在一个独立的进程里,不是每次 ps 都能找到这个进程
看一下 logcatd.rc,这个守护进程为 late_start 且默认情况下 disabled,即此服务默认不启动,而且每次启动都会把 pid 写到 /dev/cpuset/system-background/tasks 文件中,oom_score_adjust 设置为 -600 不容易被 LMK 杀死
service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 1024 -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
class late_start
disabled
# logd for write to /data/misc/logd, log group for read from log daemon
user logd
group log
writepid /dev/cpuset/system-background/tasks
oom_score_adjust -600
此服务可以在 root 权限下运行 start logcatd 开启,再找下这个进程就有了
logd 7012 1 9252 1880 __skb_recv_datagram 77a9f72f80 S logcatd
logcatd 的启动从 logcat_main.cpp 开始,流程非常清晰:调用 create_android_logcat
初始化 android_logcat_context 实例,然后运行 android_logcat_run_command
接收参数执行指定命令,android_logcat_destroy
结束此进程,具体实现都在 logcat.cpp 中
int main(int argc, char** argv, char** envp) {
android_logcat_context ctx = create_android_logcat();
if (!ctx) return -1;
signal(SIGPIPE, exit);
int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
int ret = android_logcat_destroy(&ctx);
if (!ret) ret = retval;
return ret;
}
先看 create_android_logcat
,首先初始化一个 android_logcat_context_internal 上下文指针,为其分配内存,初始化部分变量的值,然后返回。
这个 android_logcat_context_internal 结构体与 logcat 实例相关联,里面的对象都是日志打印相关
android_logcat_context create_android_logcat() {
android_logcat_context_internal* context;
context = (android_logcat_context_internal*)calloc(
1, sizeof(android_logcat_context_internal));
if (!context) return nullptr;
context->fds[0] = -1;
context->fds[1] = -1;
context->output_fd = -1;
context->error_fd = -1;
context->maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
context->argv_hold.clear();
context->args.clear();
context->envp_hold.clear();
context->envs.clear();
return (android_logcat_context)context;
}
相应的,android_logcat_destroy
方法完成的工作就是销毁前面初始化的上下文对象,处理相关变量
继续看 android_logcat_run_command
,这里会接收命令行参数,也很简单,还是把参数全设置到上面的 android_logcat_context_internal 结构体中,然后会去执行 __logcat(context)
int android_logcat_run_command(android_logcat_context ctx,
int output, int error,
int argc, char* const* argv,
char* const* envp) {
android_logcat_context_internal* context = ctx;
context->output_fd = output;
context->error_fd = error;
context->argc = argc;
context->argv = argv;
context->envp = envp;
context->stop = false;
context->thread_stopped = false;
return __logcat(context);
}
__logcat(context)
这个函数包含处理命令的核心代码,有接近一千行,流程比较清晰:
首先初始化一些变量和参数,然后在一个循环中完成“解析”工作,其中主要逻辑是调用 getopt_long_r
获取命令行参数,保存到 getopt_context 结构体中
static int __logcat(android_logcat_context_internal* context) {
......
for (;;) {
......
ret = getopt_long_r(argc, argv, ":cdDhLt:T:gG:sQf:r:n:v:b:BSpP:m:e:",
long_options, &option_index, &optctx);
......
}
......
}
然后根据返回的不同结果进不同的分支走不同的处理参数,如命令行参数 “-s” 会设置默认过滤器为 silent,会调用 android_log_addFilterRule
解析传入的参数,修改系统优先级设置,进而在后面的日志输出过程实现过滤,其他的 case 也类似,都是基于解析的参数配置到前面初始化的变量中
switch (ret) {
......
case 's':
// default to all silent
android_log_addFilterRule(context->logformat, "*:s");
break;
......
}
接下来是使用这些变量来执行逻辑了
这里是检查 context->devices,这是一个 log_device_t 结构的链表,如果链表为空,就放入 main/system/crash,也即默认缓冲区,类似的还有设置过滤条件、打印格式、黑白名单等
static int __logcat(android_logcat_context_internal* context) {
......
if (!context->devices) {
dev = context->devices = new log_device_t("main", false);
context->devCount = 1;
if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
dev = dev->next = new log_device_t("system", false);
context->devCount++;
}
if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
dev = dev->next = new log_device_t("crash", false);
context->devCount++;
}
}
......
}
接下来在一个 while 循环里,只要 context->stop 不为 True 且没有设置最大打印量 maxCount 或者设置了但是还没到预设的值,就会循环去调用 android_logger_list_read
读取日志信息,放到结构体 log_msg 中,最后调用 printBinary
或者 processBuffer
去打印日志的内容。
static int __logcat(android_logcat_context_internal* context) {
......
while (!context->stop && (!context->maxCount || (context->printCount < context->maxCount))) {
struct log_msg log_msg;
int ret = android_logger_list_read(logger_list, &log_msg);
......
if (context->printBinary) {
printBinary(context, &log_msg);
} else {
processBuffer(context, dev, &log_msg);
}
}
......
}
这里的 android_logger_list_read 是 liblog 目录下的源文件,也即流程又会走到 liblog.so,包括其他清除日志(android_logger_clear
)、获取缓冲区大小(android_logger_get_log_size
)的方法最后也是调用到了 liblog.so 中的 logger_read.c 文件中,在此略过。
首先看下日志读取过程,主要做了这么几件事:将 logger_list 强转为 android_log_logger_list 结构对象 logger_list_internal,然后构造 android_log_transport_context 对象 transp 并通过 init_transport_context
和 node_to_item
完成初始化,最后会调用 android_transport_read
执行真正的读日志
/* Read from the selected logs */
LIBLOG_ABI_PUBLIC int android_logger_list_read(struct logger_list* logger_list, struct log_msg* log_msg) {
struct android_log_transport_context* transp;
struct android_log_logger_list* logger_list_internal = (struct android_log_logger_list*)logger_list;
int ret = init_transport_context(logger_list_internal);
/* at least one transport */
transp = node_to_item(logger_list_internal->transport.next, struct android_log_transport_context, node);
......
/* if only one, no need to copy into transport_context and merge-sort */
return android_transport_read(logger_list_internal, transp, log_msg);
}
而 android_transport_read
则进一步调用 *transp->transport 对象的 read
方法
/* Validate log_msg packet, read function has already been null checked */
static int android_transport_read(struct android_log_logger_list* logger_list,
struct android_log_transport_context* transp,
struct log_msg* log_msg) {
int ret = (*transp->transport->read)(logger_list, transp, log_msg);
......
}
具体看下上面涉及到的数据结构:
首先是 android_logger_list_read
的参数 logger_list,是 logger_list 结构,没有找到在哪儿定义;
接下来是 android_log_logger_list,logger_list 一进来就被强转的类型,定义在 liblog/logger.h 中;
struct android_log_logger_list {
struct listnode node;
struct listnode logger;
struct listnode transport;
int mode;
unsigned int tail;
log_time start;
pid_t pid;
};
然后是 android_log_transport_context 结构,还是定义在 liblog/logger.h 中,它的父节点就是上面的 android_log_logger_list,它的初始化过程也是借助 logger_list 完成的。此外,里面还定义了两个后面会用到的结构体 transport 和 logMsg
struct android_log_transport_context {
struct listnode node;
union android_log_context context; /* zero init per-transport context */
struct android_log_logger_list* parent;
struct android_log_transport_read* transport;
unsigned logMask; /* mask of requested log buffers */
int ret; /* return value associated with following data */
struct log_msg logMsg; /* peek at upcoming data, valid if logMsg.len != 0 */
};
继续看 android_log_transport_read,它本质就是一个节点,但是内部定义了很多函数指针,如 read
、clear
、getSize
等【这里就和上面 liblog.so 逻辑中“写日志”部分相对应】
struct android_log_transport_read {
struct listnode node;
const char* name; /* human name to describe the transport */
/* Does not cause resources to be taken */
int (*available)(log_id_t logId);
void (*close)(struct android_log_logger_list* logger_list, struct android_log_transport_context* transp);
int (*read)(struct android_log_logger_list* logger_list, struct android_log_transport_context* transp, struct log_msg* log_msg);
......
};
最后是 log_msg,定义了一个 entry 和 buf 的 union,然后如果是 C++,则重载了一些符号操作
struct log_msg {
union {
unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
struct logger_entry_v4 entry;
struct logger_entry_v4 entry_v4;
struct logger_entry_v3 entry_v3;
struct logger_entry_v2 entry_v2;
struct logger_entry entry_v1;
} __attribute__((aligned(4)));
......
};
继续看 android_log_transport_read,init_transport_context 初始化过程也会调用 __android_log_config_read
定义不同的结构来读日志,包含 logdLoggerRead、pmsgLoggerRead
LIBLOG_HIDDEN void __android_log_config_read() {
if (__android_log_transport & LOGGER_LOCAL) {
extern struct android_log_transport_read localLoggerRead;
__android_log_add_transport(&__android_log_transport_read, &localLoggerRead);
}
#if (FAKE_LOG_DEVICE == 0)
if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
extern struct android_log_transport_read logdLoggerRead;
extern struct android_log_transport_read pmsgLoggerRead;
__android_log_add_transport(&__android_log_transport_read, &logdLoggerRead);
__android_log_add_transport(&__android_log_persist_read, &pmsgLoggerRead);
}
#endif
}
还是看 logdLoggerRead,它的 read 方法对应的就是 logdRead
方法
LIBLOG_HIDDEN struct android_log_transport_read logdLoggerRead = {
.node = { &logdLoggerRead.node, &logdLoggerRead.node },
.name = "logd",
.available = logdAvailable,
.version = logdVersion,
.read = logdRead,
......
};
在 logdRead 中,首先调用 logdOpen
创建 socket_local_client 客户端去连接 “/dev/socket/logdr”,然后初始化 log_msg 结构,最后调用 recv 从 socket 中接收日志并写入 log_msg 中
同理,pmsgLoggerWrite 打开的是 “/sys/fs/pstore/pmsg-ramoops-0” 的 socket
所以,liblog.so 的另一个主要工作就是读取日志
上一节说到 logcat 读完日志会放到 log_msg 结构中,然后调用 printBinary
/processBuffer
来打印
printBinary
逻辑很简单,就是调用 write 写到指定文件中,也有可能是直接输出到屏幕上
void printBinary(android_logcat_context_internal* context, struct log_msg* buf) {
size_t size = buf->len();
TEMP_FAILURE_RETRY(write(context->output_fd, buf, size));
}
processBuffer
里面首先会判断 dev->binary,如果为 True,则说明日志是二进制格式,会调用 android_log_processBinaryLogBuffer
将二进制格式的日志记录转化为 ASCII 形式,否则调用 android_log_processLogBuffer
将日志转换为 AndroidLogEntry 格式,结果也放到 AndroidLogEntry 类型的对象 entry 中,接下来调用 android_log_shouldPrintLine
则通过一系列过滤、正则条件判断此条日志是否需要输出,匹配通过就调用 android_log_printLogLine
打印日志
static void processBuffer(android_logcat_context_internal* context,log_device_t* dev, struct log_msg* buf) {
AndroidLogEntry entry;
if (dev->binary) {
if (!context->eventTagMap && !context->hasOpenedEventTagMap) {
context->eventTagMap = android_openEventTagMap(nullptr);
context->hasOpenedEventTagMap = true;
}
err = android_log_processBinaryLogBuffer(&buf->entry_v1, &entry, context->eventTagMap, binaryMsgBuf, sizeof(binaryMsgBuf));
} else {
err = android_log_processLogBuffer(&buf->entry_v1, &entry);
}
if (android_log_shouldPrintLine(context->logformat, std::string(entry.tag, entry.tagLen).c_str(), entry.priority)) {
bool match = regexOk(context, entry);
context->printCount += match;
if (match || context->printItAnyways) {
android_log_printLogLine(context->logformat, context->output_fd, &entry);
}
}
}