logd 读日志过程分析

这是一个介绍 Android 平台日志系统的系列文章:

  • Android 平台日志系统整体框架
  • logd 守护进程初始化过程
  • 客户端写日志过程分析
  • logd 写日志过程分析一
  • logd 写日志过程分析二
  • logd 读日志过程分析(本文)

本文基于 AOSP android-10.0.0_r41 版本讲解

LogReader 初始化

在 logd 的 main 函数中初始化了一个 LogReader 对象:

cpp 复制代码
// system/core/logd/main.cpp
int main(int argc, char* argv[]) {
    // ...

    // LogReader listens on /dev/socket/logdr. When a client
    // connects, log entries in the LogBuffer are written to the client.

    LogReader* reader = new LogReader(logBuf);
    if (reader->startListener()) {
        exit(1);
    }

    // ...
}

先看 LogReader 的构造函数:

cpp 复制代码
// system/core/logd/LogReader.h
class LogReader : public SocketListener {
    LogBuffer& mLogbuf;
    // ...
};

// system/core/logd/LogReader.cpp
LogReader::LogReader(LogBuffer* logbuf)
    : SocketListener(getLogSocket(), true), mLogbuf(*logbuf) {
}

// system/core/logd/LogReader.cpp
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;
}
  • 调用 getLogSocket 获取到 /dev/socket/logdr 的 fd,这个 Socket 在 rc 中文件中定义了,init 进程会为我们做初始化工作。
  • 接着调用了父类 SocketListener 的构造函数,然后使用传入的参数构造
  • 最后使用传入的 logbuf 参数给成员 mLogBuf 赋值

接着看下父类 SocketListener 的构造函数:

cpp 复制代码
SocketListener::SocketListener(int socketFd, bool listen) {
    init(nullptr, socketFd, listen, false);
}

接着调用 init:

cpp 复制代码
void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
    mListen = listen;
    mSocketName = socketName;
    mSock = socketFd;
    mUseCmdNum = useCmdNum;
    pthread_mutex_init(&mClientsLock, nullptr);
}

主要做一些内部成员的初始化,注意下,这里 mSock 不等于 0,有实际的值,mListen 的值是 true,其他都是 nullptr

接着调用父类的 startListener 函数:

cpp 复制代码
// system/core/libsysutils/src/SocketListener.cpp

int SocketListener::startListener() {
    return startListener(4);
}

int SocketListener::startListener(int backlog) {

    // 下面两个分支都不进入
    if (!mSocketName && mSock == -1) {
        SLOGE("Failed to start unbound listener");
        errno = EINVAL;
        return -1;
    } else if (mSocketName) {
        if ((mSock = android_get_control_socket(mSocketName)) < 0) {
            SLOGE("Obtaining file descriptor socket '%s' failed: %s",
                 mSocketName, strerror(errno));
            return -1;
        }
        SLOGV("got mSock = %d for %s", mSock, mSocketName);
        fcntl(mSock, F_SETFD, FD_CLOEXEC);
    }

    if (mListen && listen(mSock, backlog) < 0) { //走这个分支,开始 Listen
        SLOGE("Unable to listen on socket (%s)", strerror(errno));
        return -1;
    } else if (!mListen)
        mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);

    if (pipe2(mCtrlPipe, O_CLOEXEC)) { // 初始化一个 pipe
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    // 启动一个新线程 threadStart
    if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}
  • 调用 socket 的 listen
  • 接着初始化一个 pipe
  • 最后开启新线程 threadStart
cpp 复制代码
void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);

    me->runListener(); 
    pthread_exit(nullptr);
    return nullptr;
}

接着调用到 SocketListener 的 runListener 函数:

cpp 复制代码
void SocketListener::runListener() {
    while (true) {
        std::vector<pollfd> fds;

        pthread_mutex_lock(&mClientsLock);
        fds.reserve(2 + mClients.size());
        // 把 pipe 放到 vector 中
        fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
        // 把服务端 socket fd 放入 socket
        if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
        
        // mClients 当前为空,所以不进循环
        for (auto pair : mClients) {
            // NB: calling out to an other object with mClientsLock held (safe)
            const int fd = pair.second->getSocket();
            if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
            fds.push_back({.fd = fd, .events = POLLIN});
        }
        pthread_mutex_unlock(&mClientsLock);

        SLOGV("mListen=%d, mSocketName=%s", mListen, mSocketName);
        // 调用 poll,等待 fd 的事件到来
        int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
        if (rc < 0) {
            SLOGE("poll failed (%s) mListen=%d", strerror(errno), mListen);
            sleep(1);
            continue;
        }

        // pipe 事件到来
        if (fds[0].revents & (POLLIN | POLLERR)) {
            char c = CtrlPipe_Shutdown;
            TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
            if (c == CtrlPipe_Shutdown) {
                break;
            }
            continue;
        }

        // socket fd 有数据了
        if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
            // socket accept
            int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
            if (c < 0) {
                SLOGE("accept failed (%s)", strerror(errno));
                sleep(1);
                continue;
            }
            pthread_mutex_lock(&mClientsLock);
            // 新构建一个 SocketClient
            mClients[c] = new SocketClient(c, true, mUseCmdNum);
            pthread_mutex_unlock(&mClientsLock);
        }

        // Add all active clients to the pending list first, so we can release
        // the lock before invoking the callbacks.
        
        // 收集有数据的 SocketClient
        std::vector<SocketClient*> pending;
        pthread_mutex_lock(&mClientsLock);
        const int size = fds.size();
        for (int i = mListen ? 2 : 1; i < size; ++i) {
            const struct pollfd& p = fds[i];
            if (p.revents & (POLLIN | POLLERR)) {
                auto it = mClients.find(p.fd);
                if (it == mClients.end()) {
                    SLOGE("fd vanished: %d", p.fd);
                    continue;
                }
                SocketClient* c = it->second;
                pending.push_back(c);
                c->incRef();
            }
        }
        pthread_mutex_unlock(&mClientsLock);

        // 处理 socket 收到的数据
        for (SocketClient* c : pending) {
            // Process it, if false is returned, remove from the map
            SLOGV("processing fd %d", c->getSocket());
            if (!onDataAvailable(c)) {
                release(c, false);
            }
            c->decRef();
        }
    }
}

整个函数分为以下几步:

  • 把 pipe fd 放到 poll 数组中
  • 把服务端 socket fd 放入 poll 数组中
  • 调用 poll,等待 fd 的事件到来
  • 当 fd 有数据时,处理数据

对于 pipe fd,读出数据,如果数据是 CtrlPipe_Shutdown,那么退出处理数据的循环。

对于 socket fd,当有数据时,会调用 accept4 得到一个代表客户端的 fd,接着使用这个 fd 构建一个 SocketClient 对象并添加到 mClients 中,接着遍历 mClients,当对应的 fd 有数据时,调用 LogReader 来处理。

LogReader 调用 onDataAvailable 函数处理收到的谁

接着就会调用子类(LogReader)的 onDataAvailable,这个函数很长我们分步来看:

onDataAvailable 第一部分:

cpp 复制代码
// system/core/logd/LogReader.cpp

// Note returning false will release the SocketClient instance.
bool LogReader::onDataAvailable(SocketClient* cli) {
    static bool name_set;
    // prctl 系统调用用于设置进程相关的一些东西。这里使用 PR_SET_NAME 设置了线程的名字为 logd.reader
    if (!name_set) {
        prctl(PR_SET_NAME, "logd.reader");
        name_set = true;
    }

    char buffer[255];

    // 从 socket 中读出数据
    int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
    if (len <= 0) {
        doSocketDelete(cli);
        return false;
    }
    buffer[len] = '\0';

    // Clients are only allowed to send one command, disconnect them if they
    // send another.
    LogTimeEntry::wrlock();
    // 刚开始 mTimes 是空的
    for (const auto& entry : mLogbuf.mTimes) {
        if (entry->mClient == cli) {
            entry->release_Locked();
            LogTimeEntry::unlock();
            return false;
        }
    }
  • prctl 系统调用用于设置进程相关的一些东西。这里使用 PR_SET_NAME 设置了线程的名字为 logd.reader
  • 从 socket 中读出数据,数据保存在 buffer 中

onDataAvailable 第二部分:

cpp 复制代码
    LogTimeEntry::unlock();

    // 解析收到的数据
    // 通信用的是文本协议
    // 收到的数据有 tail, start, timeout, logMask, pid, 和 nonblock
    // 其中,tail 表示读取 log 的最新 tail 条数据;start 是 log 的起始时间;timeout表示读取 log 前,先睡 timeout 这么一个时长
    unsigned long tail = 0;
    static const char _tail[] = " tail=";
    char* cp = strstr(buffer, _tail);
    if (cp) {
        tail = atol(cp + sizeof(_tail) - 1);
    }

    log_time start(log_time::EPOCH);
    static const char _start[] = " start=";
    cp = strstr(buffer, _start);
    if (cp) {
        // Parse errors will result in current time
        start.strptime(cp + sizeof(_start) - 1, "%s.%q");
    }

    uint64_t timeout = 0;
    static const char _timeout[] = " timeout=";
    cp = strstr(buffer, _timeout);
    if (cp) {
        timeout = atol(cp + sizeof(_timeout) - 1) * NS_PER_SEC +
                  log_time(CLOCK_REALTIME).nsec();
    }

    unsigned int logMask = -1;
    static const char _logIds[] = " lids=";
    cp = strstr(buffer, _logIds);
    if (cp) {
        logMask = 0;
        cp += sizeof(_logIds) - 1;
        while (*cp && *cp != '\0') {
            int val = 0;
            while (isdigit(*cp)) {
                val = val * 10 + *cp - '0';
                ++cp;
            }
            logMask |= 1 << val;
            if (*cp != ',') {
                break;
            }
            ++cp;
        }
    }

    pid_t pid = 0;
    static const char _pid[] = " pid=";
    cp = strstr(buffer, _pid);
    if (cp) {
        pid = atol(cp + sizeof(_pid) - 1);
    }

    bool nonBlock = false;
    if (!fastcmp<strncmp>(buffer, "dumpAndClose", 12)) {
        // Allow writer to get some cycles, and wait for pending notifications
        sched_yield();
        LogTimeEntry::wrlock();
        LogTimeEntry::unlock();
        sched_yield();
        nonBlock = true;
    }

从 buffer 中解析处数据,这里使用的是文本协议,解析出的数据主要包含了:

  • tail:类型为 unsigned long,表示读取 log 的最新 tail 条数据
  • start:类型为 log_time,表示我们要读取的日志的起始时间
  • timeout:类型为 uint64_,表示读取 log 前,先睡 timeout 这么一个时长
  • logMask:类型为 unsigned int,和日志类型相关的一个 mask 值
  • pid:类型为pid_t,发起日志读取请求进程的进程 id
  • nonBlock:类型为bool,读日志过程是否阻塞的一个标志值,logcat 不带任何参数时,是阻塞的,会占据整个终端,当带有 -d -t 等选项时,会立即输出日志,不会阻塞占据终端

我们接着看 onDataAvailable 的第三部分:

cpp 复制代码
    log_time sequence = start;
    //
    // This somewhat expensive data validation operation is required
    // for non-blocking, with timeout.  The incoming timestamp must be
    // in range of the list, if not, return immediately.  This is
    // used to prevent us from from getting stuck in timeout processing
    // with an invalid time.
    //
    // Find if time is really present in the logs, monotonic or real, implicit
    // conversion from monotonic or real as necessary to perform the check.
    // Exit in the check loop ASAP as you find a transition from older to
    // newer, but use the last entry found to ensure overlap.
    //

    // 这种有点昂贵的数据验证操作对于非阻塞和超时是必需的。传入的时间戳必须在列表的范围内,否则立即返回。这是用来防止我们在超时处理中遇到无效的时间。查找时间是否真的存在于对数中,单调或实数,根据需要从单调或实数进行隐式转换以执行检查。当发现从旧版本到新版本的转换时,尽快退出检查循环,但使用找到的最后一个条目来确保重叠。
    if (nonBlock && (sequence != log_time::EPOCH) && timeout) {
        // 初始化一个 LogFindStart 对象
        class LogFindStart {  // A lambda by another name
           private:
            const pid_t mPid;
            const unsigned mLogMask;
            bool mStartTimeSet;
            log_time mStart;
            log_time& mSequence;
            log_time mLast;
            bool mIsMonotonic;

           public:
            LogFindStart(pid_t pid, unsigned logMask, log_time& sequence,
                         bool isMonotonic)
                : mPid(pid),
                  mLogMask(logMask),
                  mStartTimeSet(false),
                  mStart(sequence),
                  mSequence(sequence),
                  mLast(sequence),
                  mIsMonotonic(isMonotonic) {
            }

            static int callback(const LogBufferElement* element, void* obj) {
                LogFindStart* me = reinterpret_cast<LogFindStart*>(obj);
                if ((!me->mPid || (me->mPid == element->getPid())) &&
                    (me->mLogMask & (1 << element->getLogId()))) {
                    log_time real = element->getRealTime();
                    if (me->mStart == real) {
                        me->mSequence = real;
                        me->mStartTimeSet = true;
                        return -1;
                    } else if (!me->mIsMonotonic || android::isMonotonic(real)) {
                        if (me->mStart < real) {
                            me->mSequence = me->mLast;
                            me->mStartTimeSet = true;
                            return -1;
                        }
                        me->mLast = real;
                    } else {
                        me->mLast = real;
                    }
                }
                return false;
            }

            bool found() {
                return mStartTimeSet;
            }

        } logFindStart(pid, logMask, sequence,
                       logbuf().isMonotonic() && android::isMonotonic(start));

        // 调用 flushTo 函数,传入上面初始化的 logFindStart 对象
        logbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli),
                         FlushCommand::hasSecurityLogs(cli),
                         logFindStart.callback, &logFindStart);

        if (!logFindStart.found()) {
            doSocketDelete(cli);
            return false;
        }
    }

nonBlock && (sequence != log_time::EPOCH) && timeout 条件成立时,会构建了一个 logFindStart 对象,logFindStart 中有一个 callback,这个 callback 是一个过滤器,用于日志的筛选。具体怎么筛选就要看下 LogBuffer::flushTo 函数的具体实现:

cpp 复制代码
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) {
    LogBufferElementCollection::iterator it;
    uid_t uid = reader->getUid();

    rdlock();

    // 根据参数,找到遍历的起始位置
    if (start == log_time::EPOCH) {
        // client wants to start from the beginning
        it = mLogElements.begin();
    } else {
        // Cap to 300 iterations we look back for out-of-order entries.
        size_t count = 300;

        // Client wants to start from some specified time. Chances are
        // we are better off starting from the end of the time sorted list.
        LogBufferElementCollection::iterator last;
        for (last = it = mLogElements.end(); it != mLogElements.begin();
             /* do nothing */) {
            --it;
            LogBufferElement* element = *it;
            if (element->getRealTime() > start) {
                last = it;
            } else if (element->getRealTime() == start) {
                last = ++it;
                break;
            } else if (!--count) {
                break;
            }
        }
        it = last;
    }

    log_time curr = start;

    LogBufferElement* lastElement = nullptr;  // iterator corruption paranoia
    static const size_t maxSkip = 4194304;    // maximum entries to skip
    size_t skip = maxSkip;
    // 开始遍历
    for (; it != mLogElements.end(); ++it) {
        LogBufferElement* element = *it;

        if (!--skip) {
            android::prdebug("reader.per: too many elements skipped");
            break;
        }
        if (element == lastElement) {
            android::prdebug("reader.per: identical elements");
            break;
        }
        lastElement = element;

        if (!privileged && (element->getUid() != uid)) {
            continue;
        }

        if (!security && (element->getLogId() == LOG_ID_SECURITY)) {
            continue;
        }

        // NB: calling out to another object with wrlock() held (safe)
        // 调用函数传入的 filter 过滤器
        if (filter) {
            int ret = (*filter)(element, arg);
            if (ret == false) { // filter 返回 false,掉过当前 log 项
                continue;
            }
            if (ret != true) {
                break;
            }
        }

        // filter 返回 true,代码继续往下走
        bool sameTid = false;
        if (lastTid) {
            sameTid = lastTid[element->getLogId()] == element->getTid();
            // Dropped (chatty) immediately following a valid log from the
            // same source in the same log buffer indicates we have a
            // multiple identical squash.  chatty that differs source
            // is due to spam filter.  chatty to chatty of different
            // source is also due to spam filter.
            lastTid[element->getLogId()] =
                (element->getDropped() && !sameTid) ? 0 : element->getTid();
        }

        unlock();

        // 接着调用 LogBufferElement 的 flushto 方法来向客户端发送日志
        // range locking in LastLogTimes looks after us
        curr = element->flushTo(reader, this, privileged, sameTid);

        if (curr == element->FLUSH_ERROR) {
            return curr;
        }

        skip = maxSkip;
        rdlock();
    }
    unlock();

    return curr;
}

LogBuffer::flushTo 函数:

  • 根据收到的数据,确定遍历的起始位置
  • 接着开始遍历所有的日志
  • 接着在遍历中,将每一个日志对象 LogBufferElement 传递给 filter 过滤器函数,如果 filter 返回 true,代码继续往下走,如果返回 false,则跳过当前 LogBufferElement 对象

我们接着看 onDataAvailable 的第四部分:

cpp 复制代码
    android::prdebug(
        "logdr: UID=%d GID=%d PID=%d %c tail=%lu logMask=%x pid=%d "
        "start=%" PRIu64 "ns timeout=%" PRIu64 "ns\n",
        cli->getUid(), cli->getGid(), cli->getPid(), nonBlock ? 'n' : 'b', tail,
        logMask, (int)pid, sequence.nsec(), timeout);

    if (sequence == log_time::EPOCH) {
        timeout = 0;
    }

    LogTimeEntry::wrlock();
    auto entry = std::make_unique<LogTimeEntry>(
        *this, cli, nonBlock, tail, logMask, pid, sequence, timeout);
    if (!entry->startReader_Locked()) {
        LogTimeEntry::unlock();
        return false;
    }

    // release client and entry reference counts once done
    cli->incRef();
    mLogbuf.mTimes.emplace_front(std::move(entry));

    // 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));

    LogTimeEntry::unlock();

    return true;
}

这里会构建一个 LogTimeEntry 对象,该对象代表了一个读 log 的客户端。

接下来,调用 entry->startReader_Locked(),启动读 log 的线程,从 LogBuffer 读取 log 后写回客户端。

最后,把 LogTimeEntry 对象,插入到 Logbuf 的成员 mTimes 中。

接下来我们来看 log 读取线程的具体实现:

cpp 复制代码
bool LogTimeEntry::startReader_Locked() {
    pthread_attr_t attr;

    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 true;
            }
        }
        pthread_attr_destroy(&attr);
    }

    return false;
}

新的线程 threadStart 的具体实现:

cpp 复制代码
void* LogTimeEntry::threadStart(void* obj) {
    prctl(PR_SET_NAME, "logd.reader.per");

    LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);

    SocketClient* client = me->mClient;

    LogBuffer& logbuf = me->mReader.logbuf();

    bool privileged = FlushCommand::hasReadLogs(client);
    bool security = FlushCommand::hasSecurityLogs(client);

    me->leadingDropped = true;

    wrlock();

    log_time start = me->mStart;

    while (!me->mRelease) {
        if (me->mTimeout.tv_sec || me->mTimeout.tv_nsec) {
            // 等 time呕吐 一段时间
            if (pthread_cond_timedwait(&me->threadTriggeredCondition,
                                       &timesLock, &me->mTimeout) == ETIMEDOUT) {
                me->mTimeout.tv_sec = 0;
                me->mTimeout.tv_nsec = 0;
            }
            if (me->mRelease) {
                break;
            }
        }

        unlock();


        if (me->mTail) {
            // 传入 FilterFirstPass 函数,这个函数返回 false ,不会真的返回 log 给客户端
            // 实际是用于计算所有符合条件的 log 的数目
            logbuf.flushTo(client, start, nullptr, privileged, security,
                           FilterFirstPass, me);
            me->leadingDropped = true;
        }

        // 传入 FilterSecondPass 函数
        // 实际的读取 log 操作
        start = logbuf.flushTo(client, start, me->mLastTid, privileged,
                               security, FilterSecondPass, me);

        wrlock();

    // ......
    return nullptr;
}

新启动的线程,首先会等待 timeout 的时间,然后调用两次 logbuf.flushTo 函数,两次的主要差别是传入的过滤器函数不同,接下来我们来看看这两个函数:

FilterFirstPass 的实现如下:

cpp 复制代码
// A first pass to count the number of elements
// 第一次遍历,计算所有符合条件的 log 的数目
int LogTimeEntry::FilterFirstPass(const LogBufferElement* element, void* obj) {
    LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);

    LogTimeEntry::wrlock();

    if (me->leadingDropped) {
        if (element->getDropped()) {
            LogTimeEntry::unlock();
            return false;
        }
        me->leadingDropped = false;
    }

    if (me->mCount == 0) {
        me->mStart = element->getRealTime();
    }

    if ((!me->mPid || (me->mPid == element->getPid())) &&
        (me->isWatching(element->getLogId()))) {
        ++me->mCount;
    }

    LogTimeEntry::unlock();

    return false;
}

这个函数返回 false ,不会真的返回 log 给客户端。实际是用于计算所有符合条件的 log 的数目。

接着看 FilterSecondPass 的实现:

cpp 复制代码
// A second pass to send the selected elements
// 第二次遍历用于向客户端返回符合条件的日志信息
int LogTimeEntry::FilterSecondPass(const LogBufferElement* element, void* obj) {
    LogTimeEntry* me = reinterpret_cast<LogTimeEntry*>(obj);

    LogTimeEntry::wrlock();

    me->mStart = element->getRealTime();

    if (me->skipAhead[element->getLogId()]) {
        me->skipAhead[element->getLogId()]--;
        goto skip;
    }

    if (me->leadingDropped) {
        if (element->getDropped()) {
            goto skip;
        }
        me->leadingDropped = false;
    }

    // Truncate to close race between first and second pass
    if (me->mNonBlock && me->mTail && (me->mIndex >= me->mCount)) {
        goto stop;
    }

    if (!me->isWatching(element->getLogId())) {
        goto skip;
    }

    if (me->mPid && (me->mPid != element->getPid())) {
        goto skip;
    }

    if (me->mRelease) {
        goto stop;
    }

    if (!me->mTail) {
        goto ok;
    }

    ++me->mIndex;

    if ((me->mCount > me->mTail) && (me->mIndex <= (me->mCount - me->mTail))) {
        goto skip;
    }

    if (!me->mNonBlock) {
        me->mTail = 0;
    }

ok:
    if (!me->skipAhead[element->getLogId()]) {
        LogTimeEntry::unlock();
        return true;
    }
// FALLTHRU

skip:
    LogTimeEntry::unlock();
    return false;

stop:
    LogTimeEntry::unlock();
    return -1;
}

void LogTimeEntry::cleanSkip_Locked(void) {
    memset(skipAhead, 0, sizeof(skipAhead));
}

可以看出,这里会根据传入的参数,来过滤日志,将符合条件的日志会返回 true。

最后我们来看真正向客户端发送日志信息的函数 LogBufferElement::flushTo 的具体实现:

cpp 复制代码
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));

    entry.hdr_size = privileged ? sizeof(struct logger_entry_v4)
                                : sizeof(struct logger_entry_v3);
    entry.lid = mLogId;
    entry.pid = mPid;
    entry.tid = mTid;
    entry.uid = mUid;
    entry.sec = mRealTime.tv_sec;
    entry.nsec = mRealTime.tv_nsec;

    struct iovec iovec[2];
    iovec[0].iov_base = &entry;
    iovec[0].iov_len = entry.hdr_size;

    char* buffer = nullptr;

    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;
}

这里首先构建一个 logger_entry_v4 结构体,然后调用 reader->sendDatav 发送数据:

cpp 复制代码
int SocketClient::sendDatav(struct iovec *iov, int iovcnt) {
    pthread_mutex_lock(&mWriteMutex);
    int rc = sendDataLockedv(iov, iovcnt);
    pthread_mutex_unlock(&mWriteMutex);

    return rc;
}

int SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) {

    if (mSocket < 0) {
        errno = EHOSTUNREACH;
        return -1;
    }

    if (iovcnt <= 0) {
        return 0;
    }

    int ret = 0;
    int e = 0; // SLOGW and sigaction are not inert regarding errno
    int current = 0;

    struct sigaction new_action, old_action;
    memset(&new_action, 0, sizeof(new_action));
    new_action.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &new_action, &old_action);

    for (;;) {
        // 通过 socket 写数据
        ssize_t rc = TEMP_FAILURE_RETRY(
            writev(mSocket, iov + current, iovcnt - current));

        if (rc > 0) {
            size_t written = rc;
            while ((current < iovcnt) && (written >= iov[current].iov_len)) {
                written -= iov[current].iov_len;
                current++;
            }
            if (current == iovcnt) {
                break;
            }
            iov[current].iov_base = (char *)iov[current].iov_base + written;
            iov[current].iov_len -= written;
            continue;
        }

        if (rc == 0) {
            e = EIO;
            SLOGW("0 length write :(");
        } else {
            e = errno;
            SLOGW("write error (%s)", strerror(e));
        }
        ret = -1;
        break;
    }

    sigaction(SIGPIPE, &old_action, &new_action);

    if (e != 0) {
        errno = e;
    }
    return ret;
}

这里最终通过 socket 写操作,将日志信息发送给客户端 logcat,logcat 收到后打印到终端,我们就看到对于的日志信息了。

参考资料

相关推荐
小镇学者3 小时前
【PHP】导入excel 报错Trying to access array offset on value of type int
android·php·excel
一笑的小酒馆6 小时前
Android11 Launcher3去掉抽屉改为单层
android
louisgeek8 小时前
Git 根据不同目录设置不同账号
android
qq_390934749 小时前
MySQL中的系统库(简介、performance_schema)
android·数据库·mysql
whysqwhw9 小时前
Kotlin Flow 实现响应式编程指南
android
二流小码农10 小时前
鸿蒙开发:一文了解桌面卡片
android·ios·harmonyos
每次的天空10 小时前
Android第十七次面试总结(Java数据结构)
android·java·面试
梁同学与Android10 小时前
Android --- Handler的用法,子线程中怎么切线程进行更新UI
android·handler·子线程更新ui·切换到主线程
Fastcv10 小时前
这TextView也太闪了,咋做的?
android
恋猫de小郭10 小时前
iOS 26 beta1 重新禁止 JIT 执行,Flutter 下的 iOS 真机 hot load 暂时无法使用
android·前端·flutter