Android日志服务是我们接触非常多的一个服务,主要用于调试以及分析bug使用,可以记录程序执行的步骤,方便我们对代码进行追踪。它也是通过rc文件描述的,由init进程解析并启动的,我们知道init的second_stage阶段解析rc文件,其中会按顺序触发三个触发器以触发各个服务的启动,分别是early_init、init、late_init,日志服务在init触发器中启动的服务,它是比ServiceManager早启动的,因此是无法使用binder的。
本文基于Android13源码
Logd 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # system/logging/logd/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 capabilities SYSLOG AUDIT_CONTROL priority 10 task_profiles ServiceCapacityLow onrestart setprop logd.ready false
rc文件中就是基本的启动,首先启动了logd服务,然后创建了三个socket。因此可以直接回到main方法中去进行查看。这些都是由init进程进行解析并执行的,最终通过执行可执行文件/system/bin/logd来启动的logd服务.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 int main (int argc, char * argv[]) { ... android::base::InitLogging ( argv, [](android::base::LogId log_id, android::base::LogSeverity severity, const char * tag, const char * file, unsigned int line, const char * message) { if (tag && strcmp (tag, "logd" ) != 0 ) { auto prefixed_message = android::base::StringPrintf ("%s: %s" , tag, message); android::base::KernelLogger (log_id, severity, "logd" , file, line, prefixed_message.c_str ()); } else { android::base::KernelLogger (log_id, severity, "logd" , file, line, message); } }); ... LogTags log_tags; PruneList prune_list; std::string buffer_type = GetProperty ("logd.buffer_type" , "serialized" ); LogStatistics log_statistics (GetBoolPropertyEngSvelteDefault("logd.statistics" ), buffer_type == "serialized" ) ; LogReaderList reader_list; LogBuffer* log_buffer = nullptr ; if (buffer_type == "chatty" ) { log_buffer = new ChattyLogBuffer (&reader_list, &log_tags, &prune_list, &log_statistics); } else if (buffer_type == "serialized" ) { log_buffer = new SerializedLogBuffer (&reader_list, &log_tags, &log_statistics); } else if (buffer_type == "simple" ) { log_buffer = new SimpleLogBuffer (&reader_list, &log_tags, &log_statistics); } else { LOG (FATAL) << "buffer_type must be one of 'chatty', 'serialized', or 'simple'" ; } LogReader* reader = new LogReader (log_buffer, &reader_list); if (reader->startListener ()) { return EXIT_FAILURE; } LogListener* swl = new LogListener (log_buffer); if (!swl->StartListener ()) { return EXIT_FAILURE; } CommandListener* cl = new CommandListener (log_buffer, &log_tags, &prune_list, &log_statistics); if (cl->startListener ()) { return EXIT_FAILURE; } SetProperty ("logd.ready" , "true" ); LogAudit* al = nullptr ; if (auditd) { int dmesg_fd = GetBoolProperty ("ro.logd.auditd.dmesg" , true ) ? fdDmesg : -1 ; al = new LogAudit (log_buffer, dmesg_fd, &log_statistics); } LogKlog* kl = nullptr ; if (klogd) { kl = new LogKlog (log_buffer, fdDmesg, fdPmesg, al != nullptr , &log_statistics); } readDmesg (al, kl); if (kl && kl->startListener ()) { delete kl; } if (al && al->startListener ()) { delete al; } TrustyLog::create (log_buffer); TEMP_FAILURE_RETRY (pause ()); return EXIT_SUCCESS; }
这里一共做了三件事,分别是初始化日志写入、创建日志buffer、启动日志socket监听。这里分别查看。
日志初始化 日志初始化,这里的初始化实际上设置了如何写入日志的过程,当使用方去设置输出日志时,实际上就是通过这里初始化时设置的方法来输出日志的。
1 2 3 4 5 void InitLogging (char * argv[], LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER, AbortFunction&& aborter = DefaultAborter) ;
其接收三个参数,后两个参数都有默认值,可以不需要传入,在logd的初始化中,实际上最主要的就是传入第二个参数logger,该方法的具体实现是在另一个文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void InitLogging (char * argv[], LogFunction&& logger, AbortFunction&& aborter) { SetLogger (std::forward<LogFunction>(logger)); SetAborter (std::forward<AbortFunction>(aborter)); if (gInitialized) { return ; } gInitialized = true ; if (argv != nullptr ) { SetDefaultTag (basename (argv[0 ])); } const char * tags = getenv ("ANDROID_LOG_TAGS" ); if (tags == nullptr ) { return ; } std::vector<std::string> specs = Split (tags, " " ); for (size_t i = 0 ; i < specs.size (); ++i) { std::string spec (specs[i]) ; if (spec.size () == 3 && StartsWith (spec, "*:" )) { switch (spec[2 ]) { case 'v' : SetMinimumLogSeverity (VERBOSE); continue ; case 'd' : SetMinimumLogSeverity (DEBUG); continue ; case 'i' : SetMinimumLogSeverity (INFO); continue ; case 'w' : SetMinimumLogSeverity (WARNING); continue ; case 'e' : SetMinimumLogSeverity (ERROR); continue ; case 'f' : SetMinimumLogSeverity (FATAL_WITHOUT_ABORT); continue ; case 's' : SetMinimumLogSeverity (FATAL_WITHOUT_ABORT); continue ; } } LOG (FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags << ")" ; } }
这里最主要的就是setLogger设置成新的logger,然后就是一些初始化过程,这与logd的设计实现是暂时无关的,可以先就到这里。然后看后面的创建日志buffer。
创建日志buffer 日志buffer决定了日志在内存中是如何存储记录的,它分为三种buffer,根据系统属性logd.buffer_type来决定使用哪种,默认情况下使用serialized。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class LogBuffer { public : virtual ~LogBuffer () {} virtual void Init () = 0 ; virtual int Log (log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char * msg, uint16_t len) = 0 ; virtual std::unique_ptr<FlushToState> CreateFlushToState (uint64_t start, LogMask log_mask) REQUIRES (logd_lock) = 0 ; virtual bool FlushTo ( LogWriter* writer, FlushToState& state, const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence, log_time realtime)>& filter) REQUIRES (logd_lock) = 0 ; virtual bool Clear (log_id_t id, uid_t uid) = 0 ; virtual size_t GetSize (log_id_t id) = 0 ; virtual bool SetSize (log_id_t id, size_t size) = 0 ; virtual uint64_t sequence () const = 0 ; };
LogBuffer定义了log的一些基本方法,不同的buffer都需要继承自LogBuffer并实现对应的方法。我们从上面的方法中可以看到,基本上每个方法都会有一个log_id_t参数,这是日志的分类参数,Android将日志按照分类拆分成多种日志,其关键字log_id_t。该值是一个枚举值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef enum log_id { LOG_ID_MIN = 0 , LOG_ID_MAIN = 0 , LOG_ID_RADIO = 1 , LOG_ID_EVENTS = 2 , LOG_ID_SYSTEM = 3 , LOG_ID_CRASH = 4 , LOG_ID_STATS = 5 , LOG_ID_SECURITY = 6 , LOG_ID_KERNEL = 7 , LOG_ID_MAX, LOG_ID_DEFAULT = 0x7FFFFFFF } log_id_t ;
其中就有我们很熟悉的main、event、crash等,其中我们应用中通过Log.d、ALOGD等打印的日志都是属于main分类的,也是最常见的日志。LogBuffer就是通过这些分类来将日志进行分类存储的。
SimpleLogBuffer SimpleLogBuffer是最基础的LogBuffer,几乎不会使用。其对应的文件:system/logging/logd/SimpleLogBuffer.cpp和system/logging/logd/SimpleLogBuffer.h。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int SimpleLogBuffer::Log (log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char * msg, uint16_t len) { ... auto sequence = sequence_.fetch_add (1 , std::memory_order_relaxed); LogInternal (LogBufferElement (log_id, realtime, uid, pid, tid, sequence, msg, len)); return len; }void SimpleLogBuffer::LogInternal (LogBufferElement&& elem) { log_id_t log_id = elem.log_id (); logs_.emplace_back (std::move (elem)); stats_->Add (logs_.back ().ToLogStatisticsElement ()); MaybePrune (log_id); reader_list_->NotifyNewLog (1 << log_id); }
SimpleLogBuffer的结构比较简单,就是使用了一个简单的list集合,每次有新log时,都是将其包装成一个LogBufferElement然后插入到集合中,并检查对应的log_id的日志是否需要删除。
SerializedLogBuffer SerializedLogBuffer是最常用的日志缓存,默认就是使用它来存储系统日志。它与SimpleLogBuffer的区别就是它是可压缩的缓存,前面简单的缓存就是将每条日志都包装成LogBufferElement保存下来,原打印的内容有多大,就会在内存中占用多少存储空间;而SerializedLogBuffer会将保存的日志进行压缩,这样就能存储更多的日志信息。它对应的头文件和实现文件在system/logging/logd/SerializedLogBuffer.h和system/logging/logd/SerializedLogBuffer.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int SerializedLogBuffer::Log (log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char * msg, uint16_t len) { ... if (len > LOGGER_ENTRY_MAX_PAYLOAD) { len = LOGGER_ENTRY_MAX_PAYLOAD; } ... auto sequence = sequence_.fetch_add (1 , std::memory_order_relaxed); auto entry = LogToLogBuffer (logs_[log_id], max_size_[log_id], sequence, realtime, uid, pid, tid,msg, len); ... MaybePrune (log_id); reader_list_->NotifyNewLog (1 << log_id); return len; }static SerializedLogEntry* LogToLogBuffer (std::list<SerializedLogChunk>& log_buffer, size_t max_size, uint64_t sequence, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char * msg, uint16_t len) { if (log_buffer.empty ()) { log_buffer.push_back (SerializedLogChunk (max_size / SerializedLogBuffer::kChunkSizeDivisor)); } auto total_len = sizeof (SerializedLogEntry) + len; if (!log_buffer.back ().CanLog (total_len)) { log_buffer.back ().FinishWriting (); log_buffer.push_back (SerializedLogChunk (max_size / SerializedLogBuffer::kChunkSizeDivisor)); } return log_buffer.back ().Log (sequence, realtime, uid, pid, tid, msg, len); }
在SerializedLogBuffer中,数据并不是直接存储在log_中的,而是存在一个新的结构SerializedLogChunk中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void SerializedLogChunk::FinishWriting () { writer_active_ = false ; CHECK_EQ (compressed_log_.size (), 0U ); CompressionEngine::GetInstance ().Compress (contents_, write_offset_, compressed_log_); LOG (VERBOSE) << "Compressed Log, buffer max size: " << contents_.size () << " size used: " << write_offset_ << " compressed size: " << compressed_log_.size (); if (reader_ref_count_ == 0 ) { contents_.Resize (0 ); } }SerializedLogEntry* SerializedLogChunk::Log (uint64_t sequence, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char * msg, uint16_t len) { auto new_log_address = contents_.data () + write_offset_; auto * entry = new (new_log_address) SerializedLogEntry (uid, pid, tid, sequence, realtime, len); memcpy (entry->msg (), msg, len); write_offset_ += entry->total_len (); highest_sequence_number_ = sequence; return entry; }
SerializedLogBuffer将数据按照log_id进行区分,每个log_id对应自己的一个数据集合,集合的每个元素中存储自己对应的日志分类数据,当存储满之后进行压缩,后续的新日志存储在新的元素中。
ChattyLogBuffer ChattyLogBuffer不是直接继承自LogBuffer,而是SimpleLogBuffer的一个子类。我们前面看过了SimpleLogBuffer的结构,整体就是一个list集合,然后所有的日志都会包装成一个LogBufferElement然后插入到集合中去。这种存储方式比较全面,能够记录所有的信息,但是比较占用空间。而ChattyLogBuffer就是对其的一种优化,它会将连续多次同样的日志进行过滤,只记录一次。从名字看它就是一种非正式的缓存,因此也一般不会使用。它对应的头文件和实现文件在system/logging/logd/ChattyLogBuffer.h和system/logging/logd/ChattyLogBuffer.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 void ChattyLogBuffer::LogInternal (LogBufferElement&& elem) { if (elem.log_id () == LOG_ID_SECURITY) { SimpleLogBuffer::LogInternal (std::move (elem)); return ; } int log_id = elem.log_id (); if (!last_logged_elements_[log_id]) { last_logged_elements_[log_id].emplace (elem); SimpleLogBuffer::LogInternal (std::move (elem)); return ; } LogBufferElement& current_last = *last_logged_elements_[log_id]; enum match_type match = Identical (elem, current_last); if (match == DIFFERENT) { if (duplicate_elements_[log_id]) { if (duplicate_elements_[log_id]->dropped_count () > 0 ) { SimpleLogBuffer::LogInternal (std::move (*duplicate_elements_[log_id])); } duplicate_elements_[log_id].reset (); SimpleLogBuffer::LogInternal (std::move (current_last)); } last_logged_elements_[log_id].emplace (elem); SimpleLogBuffer::LogInternal (std::move (elem)); return ; } if (!duplicate_elements_[log_id]) { duplicate_elements_[log_id].emplace (std::move (current_last)); last_logged_elements_[log_id].emplace (std::move (elem)); return ; } if (match == SAME_LIBLOG) { const android_log_event_int_t * current_last_event = reinterpret_cast <const android_log_event_int_t *>(current_last.msg ()); int64_t current_last_count = current_last_event->payload.data; android_log_event_int_t * elem_event = reinterpret_cast <android_log_event_int_t *>(const_cast <char *>(elem.msg ())); int64_t elem_count = elem_event->payload.data; int64_t total = current_last_count + elem_count; if (total > std::numeric_limits<int32_t >::max ()) { SimpleLogBuffer::LogInternal (std::move (current_last)); last_logged_elements_[log_id].emplace (std::move (elem)); return ; } stats ()->AddTotal (current_last.log_id (), current_last.msg_len ()); elem_event->payload.data = total; last_logged_elements_[log_id].emplace (std::move (elem)); return ; } uint16_t dropped_count = duplicate_elements_[log_id]->dropped_count (); if (dropped_count == std::numeric_limits<uint16_t >::max ()) { SimpleLogBuffer::LogInternal (std::move (*duplicate_elements_[log_id])); dropped_count = 0 ; } current_last.SetDropped (dropped_count + 1 ); duplicate_elements_[log_id].emplace (std::move (current_last)); last_logged_elements_[log_id].emplace (std::move (elem)); }
ChattyLogBuffer最主要的就是重写了LogInternal方法,在实际需要记录log信息的地方进行了拦截,这也就导致了如果打印的数据是一模一样的话,并不会实际记录,而是设置SetDropped重复次数,当下次数据不一致的时候进行输出提示。
启用socket 上面初始化完成之后,就是启用三个核心的socket,他们就是日志服务与外界进行交互的通道。
logdr 1 2 3 4 5 6 7 8 9 10 int main (int argc, char * argv[]) { ... LogReader* reader = new LogReader (log_buffer, &reader_list); if (reader->startListener ()) { return EXIT_FAILURE; } ... }
logdr是通过LogReader来启动的,首先创建了一个LogReader,然后通过startListener启用监听。它监听的就是/dev/socket/logdr也就是读取日志信息的通道,该socket是在rc文件中创建的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 LogReader::LogReader (LogBuffer* logbuf, LogReaderList* reader_list) : SocketListener (getLogSocket (), true ), log_buffer_ (logbuf), reader_list_ (reader_list) {}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的,实际的监听也是在SocketListener中完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 int SocketListener::startListener () { return startListener (4 ); }int SocketListener::startListener (int backlog) { ... if (pthread_create (&mThread, nullptr , SocketListener::threadStart, this )) { SLOGE ("pthread_create (%s)" , strerror (errno)); return -1 ; } return 0 ; }void *SocketListener::threadStart (void *obj) { SocketListener *me = reinterpret_cast <SocketListener *>(obj); me->runListener (); pthread_exit (nullptr ); return nullptr ; }void SocketListener::runListener () { while (true ) { std::vector<pollfd> fds; pthread_mutex_lock (&mClientsLock); fds.reserve (2 + mClients.size ()); fds.push_back ({.fd = mCtrlPipe[0 ], .events = POLLIN}); if (mListen) fds.push_back ({.fd = mSock, .events = POLLIN}); for (auto pair : mClients) { 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); 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 ; } if (fds[0 ].revents & (POLLIN | POLLERR)) { char c = CtrlPipe_Shutdown; TEMP_FAILURE_RETRY (read (mCtrlPipe[0 ], &c, 1 )); if (c == CtrlPipe_Shutdown) { break ; } continue ; } if (mListen && (fds[1 ].revents & (POLLIN | POLLERR))) { 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); mClients[c] = new SocketClient (c, true , mUseCmdNum); pthread_mutex_unlock (&mClientsLock); } 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); for (SocketClient* c : pending) { if (!onDataAvailable (c)) { release (c, false ); } c->decRef (); } } }
主要逻辑就是通过SocketListener进行监听logdr套接字的数据信息以及管道数据信息,其中当有人连接到logdr后,就会建立连接并获取到socketFd,最终创建出一个SocketClient,然后在onDataAvailable中进行处理。但是该方法是个虚函数,最终的实现是在LogReader中处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 bool LogReader::onDataAvailable (SocketClient* cli) { ... char buffer[255 ]; int len = read (cli->getSocket (), buffer, sizeof (buffer) - 1 ); if (len <= 0 ) { DoSocketDelete (cli); return false ; } buffer[len] = '\0' ; if (DoSocketDelete (cli)) { return false ; } ... return true ; }
logdw logdw是写入日志信息的socket,也是在rc文件中启动的。
1 2 3 4 5 6 7 8 9 10 int main (int argc, char * argv[]) { ... LogListener* swl = new LogListener (log_buffer); if (!swl->StartListener ()) { return EXIT_FAILURE; } ... }
同样的,也是通过listener开启一个线程,然后在单独的线程中处理日志数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 LogListener::LogListener (LogBuffer* buf) : socket_ (GetLogSocket ()), logbuf_ (buf) {}int LogListener::GetLogSocket () { static const char socketName[] = "logdw" ; int sock = android_get_control_socket (socketName); if (sock < 0 ) { 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; }
注意这里和LogReader是不一样,他没有继承自SocketListener,就是一个普通的类而已。接着继续看startListener。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 bool LogListener::StartListener () { if (socket_ <= 0 ) { return false ; } auto thread = std::thread (&LogListener::ThreadFunction, this ); thread.detach (); return true ; }void LogListener::ThreadFunction () { prctl (PR_SET_NAME, "logd.writer" ); while (true ) { HandleData (); } }void LogListener::HandleData () { __attribute__((uninitialized)) char buffer[sizeof (android_log_header_t ) + LOGGER_ENTRY_MAX_PAYLOAD + 1 ]; struct iovec iov = {buffer, sizeof (buffer) - 1 }; alignas (4 ) char control[CMSG_SPACE (sizeof (struct ucred))]; struct msghdr hdr = { nullptr , 0 , &iov, 1 , control, sizeof (control), 0 , }; ssize_t n = recvmsg (socket_, &hdr, 0 ); if (n <= (ssize_t )(sizeof (android_log_header_t ))) { return ; } ... logbuf_->Log (logId, header->realtime, cred->uid, cred->pid, header->tid, msg, ((size_t )n <= UINT16_MAX) ? (uint16_t )n : UINT16_MAX); }
最终的结果就是其他地方通过logdw写入日志数据,最终由logd存储在的对应的缓存结构中。
logd logd套接字是负责命令相关的,最主要的用途就是logcat了,每次通过logcat发出命令后,都会给logd发送对应的数据,最终解析出来并进行处理。
1 2 3 4 5 6 7 8 9 10 int main (int argc, char * argv[]) { ... CommandListener* cl = new CommandListener (log_buffer, &log_tags, &prune_list, &log_statistics); if (cl->startListener ()) { return EXIT_FAILURE; } ... }
和前面一样,都是通过创建Listener并开启监听,最终来到子线程中进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CommandListener::CommandListener (LogBuffer* buf, LogTags* tags, PruneList* prune, LogStatistics* stats) : FrameworkListener (getLogSocket ()), buf_ (buf), tags_ (tags), prune_ (prune), stats_ (stats) { registerCmd (new ClearCmd (this )); registerCmd (new GetBufSizeCmd (this )); registerCmd (new SetBufSizeCmd (this )); registerCmd (new GetBufSizeReadableCmd (this )); registerCmd (new GetBufSizeUsedCmd (this )); registerCmd (new GetStatisticsCmd (this )); registerCmd (new SetPruneListCmd (this )); registerCmd (new GetPruneListCmd (this )); registerCmd (new GetEventTagCmd (this )); registerCmd (new ReinitCmd (this )); registerCmd (new ExitCmd (this )); }int CommandListener::getLogSocket () { static const char socketName[] = "logd" ; int sock = android_get_control_socket (socketName); if (sock < 0 ) { sock = socket_local_server ( socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); } return sock; }
构造方法中注册了多个命令,后续就会解析套接字中的命令数据,然后交给对应的命令进行处理,这里就不在展开了。其中CommandListener是继承自FrameworkListener的,然后在FrameworkListener又继承自SocketListener,因此它和前面的LogReader是有些相似的。
当CommandListener启动之后,就代表了logd服务已经完成了启动,此时会在系统属性中写入SetProperty("logd.ready", "true");。
使用 logdr和logdw提供了对日志的读写功能,是通过socket进行交互的,但是实际上我们肯定不可能直接对其进行操作,一方面是权限问题,另一方面则是协议问题。直接交互无法保障数据格式以及交互方的身份情况,因此提供了一系列的方法供我们使用。
在Java层,log的写入是通过一系列的静态方法去写入的,如Log.d等,我们直接通过这些静态方法打印日志即可,不需要关注身份以及格式问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public final class Log { public static int d (@Nullable String tag, @NonNull String msg) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } public static int v (@Nullable String tag, @NonNull String msg) { return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); } ... @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static int logToRadioBuffer (@Level int priority, @Nullable String tag, @Nullable String message) { return println_native(LOG_ID_RADIO, priority, tag, message); } }
可以看到,我们正常通过Log打印的日志都是通过println_native走到native层进行打印的,并且分类是LOG_ID_MAIN。分类id是固定的,不允许指定,如果想要打印别的id,则需要使用其他工具类进行打印。如SLog打印的是SYSTEM类型的日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 public final class SLog { @UnsupportedAppUsage public static int v (@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg); } @UnsupportedAppUsage public static int d (@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg); } ... }
实际上还是走到了Log类中进行的打印,只是换了个ID而已。注意SLog在普通的应用中是不允许使用的,应该是在系统组件中使用。EVENT类型的日志是在另外一个文件中打印的。
1 2 3 4 5 6 7 public final class EventLog { public static native int writeEvent (int tag, int value) ; public static native int writeEvent (int tag, long value) ; public static native int writeEvent (int tag, String str) ; ... }
主要分为了两个部分进入到native层进行打印,一个是println_native一个是writeEvent,接下来继续看这两个方法的实现。因为这两个方法都是在Java层中的,对于framework层的jni方法都是动态注册的,他们对应的文件都在frameworks/base/core/jni/目录下,因此我们如果想要找对应的jni文件,则可以直接在该目录下搜包名_类名.cpp文件,如这里我们就应该找android.util.Log.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 }, };static jint android_util_Log_println_native (JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj) { const char * tag = NULL ; const char * msg = NULL ; if (msgObj == NULL ) { jniThrowNullPointerException (env, "println needs a message" ); return -1 ; } if (bufID < 0 || bufID >= LOG_ID_MAX) { jniThrowNullPointerException (env, "bad bufID" ); return -1 ; } if (tagObj != NULL ) tag = env->GetStringUTFChars (tagObj, NULL ); msg = env->GetStringUTFChars (msgObj, NULL ); int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL ) env->ReleaseStringUTFChars (tagObj, tag); env->ReleaseStringUTFChars (msgObj, msg); return res; }
jni层中的方法只是一个转发,实际上还是走到了liblog中的方法,该方法定义在system/logging/liblog/include/android/log.h中,然后被包含在了system/logging/liblog/include/log/log.h中,实际的实现文件在system/logging/liblog/logger_write.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int __android_log_buf_write(int bufID, int prio, const char * tag, const char * msg) { ... __android_log_message log_message = { sizeof (__android_log_message), bufID, prio, tag, nullptr , 0 , msg}; __android_log_write_log_message(&log_message); return 1 ; }void __android_log_write_log_message(__android_log_message* log_message) { ... logger_function (log_message); }static __android_logger_function logger_function = __android_log_logd_logger;void __android_log_logd_logger(const struct __android_log_message* log_message) { int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id; struct iovec vec[3 ]; vec[0 ].iov_base = const_cast <unsigned char *>(reinterpret_cast <const unsigned char *>(&log_message->priority)); vec[0 ].iov_len = 1 ; vec[1 ].iov_base = const_cast <void *>(static_cast <const void *>(log_message->tag)); vec[1 ].iov_len = strlen (log_message->tag) + 1 ; vec[2 ].iov_base = const_cast <void *>(static_cast <const void *>(log_message->message)); vec[2 ].iov_len = strlen (log_message->message) + 1 ; write_to_log (static_cast <log_id_t >(buffer_id), vec, 3 ); }static int write_to_log (log_id_t log_id, struct iovec* vec, size_t nr) { int ret; ... ret = LogdWrite (log_id, &ts, vec, nr); PmsgWrite (log_id, &ts, vec, nr); return ret; }
反正就是经过一系列的方法调用以及数据和权限检测等,最后走到的是LogdWrite来实际进行打印。
1 2 3 4 5 6 7 8 9 10 11 12 13 int LogdWrite (log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) { ... ret = TEMP_FAILURE_RETRY (writev (logd_socket.sock (), newVec, i)); if (ret < 0 && errno != EAGAIN) { logd_socket.Reconnect (); ret = TEMP_FAILURE_RETRY (writev (logd_socket.sock (), newVec, i)); } ... }
最终实际上还是往/dev/socket/logdw中写入的数据,然后经由logd服务的处理,最终存储在日志缓存中。以上的整个链路就是Java层打印普通log的一个流程,而对应EVENT类型的日志,走的是另一条路,我们重新回过头看android_utils_EventLog.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static const JNINativeMethod gRegisterMethods[] = { { "writeEvent" , "(II)I" , (void *) ELog::writeEventInteger }, { "writeEvent" , "(IJ)I" , (void *) ELog::writeEventLong }, { "writeEvent" , "(IF)I" , (void *) ELog::writeEventFloat }, { "writeEvent" , "(ILjava/lang/String;)I" , (void *) ELog::writeEventString }, { "writeEvent" , "(I[Ljava/lang/Object;)I" , (void *) ELog::writeEventArray }, { "readEvents" , "([ILjava/util/Collection;)V" , (void *) android_util_EventLog_readEvents }, { "readEventsOnWrapping" , "([IJLjava/util/Collection;)V" , (void *) android_util_EventLog_readEventsOnWrapping }, };
对应的native方法被映射到了ELog类中去了,该类是在eventlog_helper.h中定义的。它不像MAIN类型的日志那样全部当成了字符串,而是将打印的日志根据数据类型进行区分了,对应的方法也有好几个方法,不过都基本上差不多,我们看一个即可。
1 2 3 4 5 6 7 8 9 10 11 12 class EventLogHelper {public : static jint writeEventInteger (JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED, jint tag, jint value) { android_log_event_list ctx (tag) ; ctx << (int32_t )value; return ctx.write (LogID); } ... }
看流程就是构建了一个ctx的list,然后写入了一个int值,在然后通过write写入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class android_log_event_list { ... public : ... android_log_event_list& operator <<(int32_t value) { int retval = android_log_write_int32 (ctx, value); if (retval < 0 ) ret = retval; return *this ; } ... int write (log_id_t id = LOG_ID_EVENTS) { if ((ret == -EBUSY) || (ret > 0 )) ret = 0 ; int retval = android_log_write_list (ctx, id); if (!ret) ret = retval; return ret; } ... }
继续跟踪,这两个方法实际调用的方法实现不在头文件中,而是在对应的实现文件中,继续追踪即可查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int android_log_write_int32 (android_log_context context, int32_t value) { ... context->count[context->list_nest_depth]++; auto * event_int = reinterpret_cast <android_event_int_t *>(&context->storage[context->pos]); event_int->type = EVENT_TYPE_INT; event_int->data = value; context->pos += needed; return 0 ; }int android_log_write_list (android_log_context context, log_id_t id) { const char * msg; ssize_t len; ... return (id == LOG_ID_EVENTS) ? __android_log_bwrite(context->tag, msg, len) : ((id == LOG_ID_STATS) ? __android_log_stats_bwrite(context->tag, msg, len) : __android_log_security_bwrite(context->tag, msg, len)); }
最终还是走到了logger_write中,与前面的保持了一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __android_log_bwrite(int32_t tag, const void * payload, size_t len) { ErrnoRestorer errno_restorer; struct iovec vec[2 ]; vec[0 ].iov_base = &tag; vec[0 ].iov_len = sizeof (tag); vec[1 ].iov_base = (void *)payload; vec[1 ].iov_len = len; return write_to_log (LOG_ID_EVENTS, vec, 2 ); }
也就是说,实际上在Java层的几种的类型的日志,实际上都会通过jni走到native层,然后最终汇聚在logger_write中,由其向/dev/socket/logdw写入数据,最终在logd服务中保存。实际上,logger就是logd提供的给外界其他应用或进程使用的一个日志接口。
那么我们在开发jni层面的代码时,打印log也需要引入logger吗?当然可以:
1 2 3 4 5 6 7 #include <log/log.h> __android_log_buf_write(bufID, priority, tag, msg); __android_log_write(int priority, tag, msg)
但是我们实际上不这么用,而是使用给定的打印日志的宏来进行日志打印,如我们熟悉的ALOG,它是定义的一系列宏,可以帮助我们快速打印日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef ALOGD #define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) #endif #ifndef ALOG #define ALOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) #endif #ifndef LOG_PRI #define LOG_PRI(priority, tag, ...) android_printLog(priority, tag, __VA_ARGS__) #endif #define android_printLog(prio, tag, ...) \ __android_log_print(prio, tag, __VA_ARGS__)
该方法定义在android/log.h,实际实现在logger_writer.cpp中。具体就不再看了,最终还是走到了同样的逻辑中,只是使用这种方式,我们写起来比较简单,还可以使用占位符来生成字串等信息。同时也帮我们省去了priority和tag参数,我们只需要关注后面的message即可。如我们想打印DEBUG类型的日志,就可以使用ALOGD;若是打印ERROR类型的日志,就可以使用ALOGE。如下:
1 2 3 4 5 #include <log/log.h> #define LOG_TAG "MyTAG" ALOGD ("打印日志" );ALOGI ("打印日志:%d" , 20 );
其他的如SLOG、RLOG等打印的是对应其他ID的日志信息,使用方式上和ALOG一模一样,这里不在多赘述了。当然,对于应用层的jni开发,我们可能会无法引入log/log.h头文件,我们只能引入android/log.h头文件,因此我们只能使用__android_log_print来打印日志,或者模仿ALOG去自己定义一些宏。
但是,在最新的NDK中,已经集成了android/log_macros.h头文件,因此我们引入该头文件后就可以使用ALOG了。
总结 至此,我们基本上看完了logd的实现以及使用,实际上我们最主要关注的就是logd的实现以及日志的写入,对于日志的读取基本上没怎么查看。这是因为在我们大多数的场景下,我们都不需要直接读取日志信息,而是通过logcat等方式去查看日志内容。
logd作为init进程中较早启动的进程,它是在ServiceManager前启动的,因此也没有采用binder的方式进行跨进程(后面又引入了binder,只是在比较晚的时候才去注册了自身的服务),而是使用了socket的方式。一共提供了三个套接字,分别是logdr负责读取日志,logdw负责写入日志,logd负责处理logcat命令等。