Android_8.1 Log 系统源码分析

文章目录

      • 0x01 [Android Log框架推荐](https://www.jianshu.com/p/64b63e51fd4c)
        • 1、 [logger](https://github.com/orhanobut/logger)
        • 2、[timber](https://github.com/JakeWharton/timber)
        • 3、[Hugo](https://github.com/JakeWharton/hugo)
        • 4、[xLog](https://github.com/elvishew/xLog)
        • 5、 [LogUtils](https://github.com/pengwei1024/LogUtils)
        • 6、[Log4a](https://github.com/pqpo/Log4a)
      • 0x02 Android 原生 Log 系统
        • 1、Java 层逻辑
        • 2、Native 层逻辑
        • 3、liblog.so 逻辑
        • 4、logd 逻辑
          • 4.1、LogBuffer 逻辑
          • 4.2、LogListener 逻辑
          • 4.3、LogReader 逻辑
        • 5、log 流程小结
        • 6、logcat 逻辑
          • 6.1、 logcat 逻辑
          • 6.2、读日志逻辑
          • 6.3、打日志逻辑

0x01 Android Log框架推荐

1、 logger

提供了一些格式化输出、美观

// 添加依赖
implementation 'com.orhanobut:logger:2.2.0'

// 初始化
Logger.addLogAdapter(new AndroidLogAdapter());
// 使用
Logger.d("hello,Android");

2、timber

基于原生Log类的小型可扩展的log框架

3、Hugo

使用注解形式的调试版本log框架

4、xLog

可扩展,支持多种数据格式,支持线程和调用栈信息

5、 LogUtils

支持多种数据结构,支持系统对象,支持高性能写入文件(mmap)

6、Log4a

基于mmap内存映射,最大化保证日志完整性

0x02 Android 原生 Log 系统

1、Java 层逻辑

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

  • 检查 tag,取tag,调用 isLoggable 判断
    • 调用 __android_log_is_loggable 获取 logLevel【/system/core/liblog/properties.c,编译成 liblog.so】
    • 比较 prio 和 logLevel

android_util_Log_println_native

  • 检查bufID,检查 tag 和 msg,取 tag 和 msg,最后调用 __android_log_buf_write【/system/core/liblog/logger_write.c,编译到 liblog.so】

2、Native 层逻辑

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 中

  • 首先定义 va_list 类型变量,是一个指向参数的指针
  • va_start 初始化变量,此函数的第一个参数是指针,第二个参数是固定参数
  • vsnprintf 将可变参数格式化输出到一个字符数组
  • 另外还有 va_arg 获取可变的参数,此函数的第一个参数是指针,第二个参数是可变参数类型
  • va_end 结束可变参数的获取

最后函数会调用 __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++ 层就走到同一个函数,在这个函数中会实现写设备文件

3、liblog.so 逻辑

liblog.so 会被所有需要日志操作的进程加载,负责处理打印和读取日志的流程

主要代码及逻辑都在 /system/core/liblog/logger_write.c 中,__android_log_buf_write 里面做了下面这几件事:

  1. 定义 iovec 结构数组 vec[3],三个元素分别存放 prio、tag、msg

    struct iovec {
      void* iov_base;
      size_t iov_len;
    };
    

    iovec 结构体包含一个指向缓冲区的指针和读/写的长度

  2. 判断 bufID 修改 tag(bufID 这个变量表示不同的 log 缓冲区,或者理解成写到不同的文件里)

  3. 最后调用 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 的一个主要工作就是写入日志

4、logd 逻辑

使用 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 去验证下文件是不是开着


这里用了三种方法去验证:

  1. 通过访问文件表获取 flag,最快;
  2. 通过文件表去拿 file 对象,然后获取 flag,其次;
  3. 通过文件表拿到 file 对象,然后进一步拿到 inode 节点数据,再获取 stat,最慢。

__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 监听

  • 创建 LogBuffer 对象,负责管理所有的 log entries,可以理解为存放 log,对应线程“logd.auditd”
  • 创建 LogReader 对象,监听 /dev/socket/logdr,有客户端接入时就把 logBuf 中的 log entry 给客户端,即管理客户端读取 log
  • 创建 LogListener 对象,监听 /dev/socket/logdw,负责将 log entry 写入 logBuf,即写入 log
  • 创建 CommandListener 对象,监听 /dev/socket/logd,接收各种命令
  • LogAudit 读取 selinux 的日志
  • LogKlog 读取 kernel 的日志,写到 logBuf 中

看一下 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
4.1、LogBuffer 逻辑

LogBuffer 继承自 LogBufferInterface,类内部定义了很多成员变量和一些函数,包括:

  • LogBufferElementCollection
  • LogBufferElement 指针变量:lastLoggedElements 和 droppedElements
  • LastLogTimes
  • 构造和析构方法,init/log 方法

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,这个方法的作用是,发送条件变量去唤醒另一个处于阻塞状态的线程,辅助后续的日志读写

4.2、LogListener 逻辑

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

  • new LogListener()
    • new SocketListener()
      • SocketListener::init()
  • LogListener->startListener()
    • SocketListener::startListener()
      • SocketListener::threadStart()
        • SocketListener::runListener()
          • LogListener::onDataAvailable()
            • LogBuffer::log()
              • push_back(LogBufferElement)
            • LogReader::notifyNewLog()
4.3、LogReader 逻辑

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

  • new LogReader()
    • new SocketListener()
      • SocketListener::init()
  • LogReader->startListener()
    • SocketListener::startListener()
      • SocketListener::threadStart()
        • SocketListener::runListener()
          • LogReader::onDataAvailable()
            • FlushCommand::runSocketCommand()
              • new LogTimeEntry()
              • LogTimeEntry::startReader_Locked()
                • LogTimeEntry::threadStart()
                  • LogBuffer::flushTo()
                  • LogBufferElement::flushTo
          • LogReader::notifyNewLog()
            • SocketListener::runOnEachSocket
              • FlushCommand::runSocketCommand

5、log 流程小结

===========================================================

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
	
===========================================================

6、logcat 逻辑

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
6.1、 logcat 逻辑

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);
        }
    }
    ......
}
6.2、读日志逻辑

这里的 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_contextnode_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,它本质就是一个节点,但是内部定义了很多函数指针,如 readcleargetSize 等【这里就和上面 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 的另一个主要工作就是读取日志

6.3、打日志逻辑

上一节说到 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);
        }
    }
}

你可能感兴趣的:(android源码学习)