欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

基于Android P Log系统学习笔记

程序员文章站 2022-06-22 08:49:09
Log机制Log 写Log 读Log使用Log的buffer sizelogcatLog机制APP打印日志,最简单的是使用Log类(android.util.Log),所以我就从一句简单的log.d开始我的路线。在APP中如果调用Log.d(“test”,“print test”); ,那么可以在Android中使用logcat看到一句我们打印的日志,如下:01-01 00:03:35.122 2990 3505 D test: print test那么这个过程是怎么样的呢,简单的看看Log....

Log机制

APP打印日志,最简单的是使用Log类(android.util.Log),所以我就从一句简单的log.d开始我的路线。在APP中如果调用Log.d(“test”,“print test”); ,那么可以在Android中使用logcat看到一句我们打印的日志,如下:

01-01 00:03:35.122  2990  3505 D test: print test

那么这个过程是怎么样的呢,简单的看看Log.java

Log 写

Log.java中定义了log的buffer ID

/** @hide */ public static final int LOG_ID_MAIN = 0;     		主要使用的类android.util.Log
/** @hide */ public static final int LOG_ID_RADIO = 1;			主要使用的类android.telephony.Rlog
/** @hide */ public static final int LOG_ID_EVENTS = 2;		主要使用的类android.util.EventLog
/** @hide */ public static final int LOG_ID_SYSTEM = 3;       主要使用的类android.util.Slog(hide)
/** @hide */ public static final int LOG_ID_CRASH = 4;         主要使用的类com.andoid.internal.os.RuntimeInit
																										和libdebugged/utility.cpp

简单的调用流程如下:
调用public static int d(String tag, String msg), 这方法调用的是println_native(LOG_ID_MAIN, DEBUG, tag, msg, tr),传入了一个LOG_ID_MAIN。然后println_native的实现交给了在frameworks/base/core/jni/android_util_Log.cpp中的android_util_Log_println_native

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
...省略
    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); //关键代码
...省略
    return res;
}

__android_log_buf_write的实现在system/core/liblog/logger_write.c中(其他一些系统模块,例如debuggered等C++代码都会直接封装或者调用__android_log_buf_write来打印日志)

LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio,
                                              const char* tag, const char* msg) {
   struct iovec vec[3];
   ...省略一大坨                                 
   vec[0].iov_base = (unsigned char*)&prio;      // 例如通过Log.v调用过来,这里是2(VERBOSE)
   vec[0].iov_len = 1;
   vec[1].iov_base = (void*)tag;
   vec[1].iov_len = strlen(tag) + 1;
   vec[2].iov_base = (void*)msg;
   vec[2].iov_len = strlen(msg) + 1;
  ...又省略一大坨
  return write_to_log(bufID, vec, 3);
}

而static int (write_to_log)(log_id_t, struct iovec vec, size_t nr) = __write_to_log_init; 默认调用__write_to_log_init

static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
  int ret, save_errno = errno;

  __android_log_lock();

  if (write_to_log == __write_to_log_init) {
    ret = __write_to_log_initialize();  		//点1

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

    write_to_log = __write_to_log_daemon;
  }

  __android_log_unlock();

  ret = write_to_log(log_id, vec, nr); 		//点2
  errno = save_errno;
  return ret;
}

这个函数中有两个点,先看一下点1,里面为集合__android_log_transport_write设置各类writer,例如logdLoggerWrite,pmsgLoggerWrite等,然后依次调用writer的open方法,logdLoggerWrite#logdOpen方法,如果打开失败,则关闭。简单的看下logdLoggerWrite的实现在system/core/liblog/logd_writer.c中

static int logdOpen() {
	...省略
      strcpy(un.sun_path, "/dev/socket/logdw");
      if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un,
                                     sizeof(struct sockaddr_un))) < 0) {
	...省略
}

大概意思就是建立socket连接
然后继续看流程就走到了点2的地方,实际的实现是__write_to_log_daemon,这个函数比较长,看一下关键的几个点

static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
  struct android_log_transport_write* node;
  int ret, save_errno;
  struct timespec ts;
  size_t len, i;
  ...
  clock_gettime(android_log_clockid(), &ts); 		//获取时间戳

  if (log_id == LOG_ID_SECURITY) {
    if (vec[0].iov_len < 4) {
      errno = save_errno;
      return -EINVAL;
    }

    ret = __android_log_is_loggable_len(ANDROID_LOG_INFO, tag, len,
                                        ANDROID_LOG_VERBOSE);
    if (f) { /* local copy marked for close */
      android_closeEventTagMap(f);
    }
    if (!ret) {
      errno = save_errno;
      return -EPERM;
    }
  } else {
    /* Validate the incoming tag, tag content can not split across iovec */
    char prio = ANDROID_LOG_VERBOSE;
    const char* tag = vec[0].iov_base;
    size_t len = vec[0].iov_len;
    // 变量prio存储vec[0].iov_base,例如2(VERBOSE),tag存储vec[1].iov_base
    if (!__android_log_is_loggable_len(prio, tag, len - 1, ANDROID_LOG_VERBOSE)) {
	//如果当前打印的log级别低于系统设置的级别,会直接返回,不会打印。默认是ANDROID_LOG_VERBOSE(2),系统设置的级别来自于属性:persist.log.tag 或 log.tag,tag就是你Log.d(TAG,“”)一般来说使用方式为
	adb shell setprop log.tag.<YOUR_LOG_TAG> <LEVEL>
  ...

   // 以下是核心方法实现
  ret = 0;
  i = 1 << log_id;
  write_transport_for_each(node, &__android_log_transport_write) {
    if (node->logMask & i) {
      ssize_t retval;
      retval = (*node->write)(log_id, &ts, vec, nr);
      if (ret >= 0) {
        ret = retval;
      }
    }
  }

  write_transport_for_each(node, &__android_log_persist_write) {
    if (node->logMask & i) {
      (void)(*node->write)(log_id, &ts, vec, nr);
    }
  }

  errno = save_errno;
  return ret;
}

循环调用所有writer的write方法来传输日志,例如logdLoggerWrite的write,对应的就是logdWrite,在之前socket已经open了,同样只看关键的地方,如果不仔细看的话,我是没看出来在哪

static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec,
                     size_t nr) {
  ...省略
  /*
   * The write below could be lost, but will never block.
   *
   * ENOTCONN occurs if logd has died.
   * ENOENT occurs if logd is not running and socket is missing.
   * ECONNREFUSED occurs if we can not reconnect to logd.
   * EAGAIN occurs if logd is overloaded.
   */
  if (sock < 0) {
    ret = sock;
  } else {
    ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i)); 		//这个地方就在通过socket写数据了
    if (ret < 0) {
      ret = -errno;
    }
  }
  ...省略
  return ret;
}

以上就是整个调用Log.d整个大概流程

Log 读

下面看一下我们是怎么通过logcat来找到我们通过log.d写的日志的,即Logd的逻辑分析,接收到socket通信传输后的数据

Logd进程是开机时由init进程启动,启动代码参考:system/core/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
    writepid /dev/cpuset/system-background/tasks

service logd-reinit /system/bin/logd --reinit
    oneshot
    disabled
    user logd
    group logd
    writepid /dev/cpuset/system-background/tasks

on fs
    write /dev/event-log-tags "# content owned by logd
"
    chown logd logd /dev/event-log-tags
    chmod 0644 /dev/event-log-tags

进程启动后,入口方法:system/core/logd/main.cpp,其中入口的main方法实现不复杂,主要创建LogBuffer,然后启动5个listener,一般重要的是前三个:LogReader,LogListener,CommandListener,全部继承于SocketListener(system/core/libsysutils),另外还有2个listener:LogAudit(监听NETLINK_AUDIT,与selinux有关),LogKlog。看下main.cpp中的main函数

// Foreground waits for exit of the main persistent threads
// that are started here. The threads are created to manage
// UNIX domain client sockets for writing, reading and
// controlling the user space logger, and for any additional
// logging plugins like auditd and restart control. Additional
// transitory per-client threads are created for each reader.
int main(int argc, char* argv[]) {
    // logd is written under the assumption that the timezone is UTC.
    // If TZ is not set, persist.sys.timezone is looked up in some time utility
    // libc functions, including mktime. It confuses the logd time handling,
    // so here explicitly set TZ to UTC, which overrides the property.
    setenv("TZ", "UTC", 1);
    
    //......
    // LogBuffer,作用:存储所有的日志信息
    logBuf = new LogBuffer(times);
    
    // LogReader listens on /dev/socket/logdr. When a client
    // connects, log entries in the LogBuffer are written to the client.
	//LogReader监听Socket(/dev/socket/logdr),当客户端连接logd后,LogReader将LogBuffer中的日志写给客户端。线程名:logd.reader,通过prctl(PR_SET_NAME, "logd.reader");设定

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

    // LogListener listens on /dev/socket/logdw for client
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.
	// LogListener监听Socket(/dev/socket/logdw),接收传来的日志信息,写入LogBuffer;同时LogReader将新的日志传给已连接的客户端。线程名:logd.writer

    LogListener* swl = new LogListener(logBuf, reader);
    // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
    if (swl->startListener(600)) {
        exit(1);
    }

    // Command listener listens on /dev/socket/logd for incoming logd
    // administrative commands.
    //CommandListener监听Socket(/dev/socket/logd),作用:接收发来的命令。线程名:logd.control
   
    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        exit(1);
    }
    
    //......
    
    exit(0);
}

LogListener,startListener 在SocketListener.cpp中实现 startListener ->SocketListener::threadStart->me->runListener()->onDataAvailable然后当有对端进程通过Socket传递过来数据后,onDataAvailable方法被调用(实现在LogListener.cpp中),其中主要是解析数据、调用LogBuffer->log方法存储日志信息,调用LogReader→notifyNewLog方法通知有新的日志信息

bool LogListener::onDataAvailable(SocketClient* cli) {
    // ......
    // 1. 调用LogBuffer->log方法存储日志信息
    int res = logbuf->log(
            logId, header->realtime, cred->uid, cred->pid, header->tid, msg,
            ((size_t)n <= USHRT_MAX) ? (unsigned short)n : USHRT_MAX);
 
    // 2. 调用LogReader→notifyNewLog方法通知有新的日志信息,以便发送给其客户端
    if (res > 0 && reader != nullptr) {
        reader->notifyNewLog(static_cast<log_mask_t>(1 << logId));
    }
    // ...
}

然后我们看LogBuffer的log方法,system/core/logd/LogBuffer.cpp

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) {
    // ...
    // 低于当前设定的日志优先级,返回
    if (!__android_log_is_loggable_len(prio, tag, tag_len,
                                       ANDROID_LOG_VERBOSE)) {
        // Log traffic received to total
        wrlock();
        stats.addTotal(elem);
        unlock();
        delete elem;
        return -EACCES;
    }
 
    // 调用重载的log方法
    log(elem);
    unlock();
 
    return len;
}

继续log方法,主要作用是通过比对新进日志信息的时间,将其插入到正确的存储位置。所有日志存储在mLogElements变量中,其类型是:typedef std::list<LogBufferElement*

void LogBuffer::log(LogBufferElement* elem) {
      // 插入正确位置,逻辑相对复杂,摘取其中关键一段
      do {
            last = it;
            if (__predict_false(it == mLogElements.begin())) {
                  break;
            }
            --it;
      } while (((*it)->getRealTime() > elem->getRealTime()) && (!end_set || (end <= (*it)->getRealTime())));
            mLogElements.insert(last, elem);
      }
     // ...
     stats.add(elem);          
      // 初步看做一些统计工作,例如通过数组,统计不同类型日志的打印次数,不同类型日志的字符串总长度等,并且将日志信息以uid, pid, tid, tag等为单位,保存elem信息至不同的hashtable中
     maybePrune(elem->getLogId());
}

其中maybePrune方法的作用很重要,当不同类型的log日志size超过最大限制时,会触发对已保存日志信息的裁剪,一次裁剪量约为10%:

void LogBuffer::maybePrune(log_id_t id) {
    size_t sizes = stats.sizes(id);                                         
    // 来自LogStatistics->mSizes[id]变量的值,统计不同日志类型的当前日志长度(msg)
    
    unsigned long maxSize = log_buffer_size(id);           
     // 取不同日志类型的日志长度最大值
     
    if (sizes > maxSize) {
        size_t sizeOver = sizes - ((maxSize * 9) / 10);
        size_t elements = stats.realElements(id);
        size_t minElements = elements / 100;
        if (minElements < minPrune) {                               // minPrune值是4
            minElements = minPrune;                                 // minElements默认是全部日志元素数的百分之一,最小值是4
        }
        unsigned long pruneRows = elements * sizeOver / sizes; 
        // 需要裁剪的元素个数,最小值是4个,最大值是256个,正常是总元素的比例:1 - (maxSize/sizes)* 0.9 = 约等于10%
        if (pruneRows < minElements) {
            pruneRows = minElements;
        }
        if (pruneRows > maxPrune) { 			  // maxPrune值是256
            pruneRows = maxPrune;
        }
        prune(id, pruneRows);                                           
        // 如果日志存储已越界,则最终走到prune裁剪函数中处理,pruneRows是需要裁剪的元素个数
    }
}

目前,就是我自己整理的android log系统的一部分流程。

小总结

Android层调用Log/Slog/Rlog中的v/d方法打印log,最终会调用到

system/core/liblog/logger_write.c 中的__android_log_buf_write函数,调用流程如下:

__android_log_buf_write

->write_to_log

           ->__write_to_log_init

                    ->__write_to_log_initialize

                             ->logdOpen

                    ->__write_to_log_daemon

                             ->logdWrite

log信息最终写到 “/dev/socket/logdw”中,此时logd中的LogListener会监测到有log需写入,当log写入成功后,会通知LogReader将新保存的log传递给logcat等。

在system/core/lodgd/main.cpp文件的main函数中,创建了LogBuffer,LogReader,LogListener和CommandListener四个对象,

  1. LogBuffer用于管理log;
  2. LogReader用于将log传递给logcat;
  3. LogListener用于监听是否有log写入logd;
  4. CommandListener用于监听是否有命令发送给logd。

log的保存流程如下:

  1. 创建LogBuffer对象,在classLogBuffer类中,定义了一个list容器,保存了指向LogBufferElement对象的指针,创建LogBuffer对象时在其构造函数中会调用LogBuffer::init()函数初始化各log域,例如main/system/kernel/crash等的大小
  2. 创建LogListener对象并开始监听
	 	LogListener *swl = newLogListener(logBuf, reader);
	
	    // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
	
	    if(swl->startListener(600)) {
	
	        exit(1);
	
	    }
  1. 在startListener函数中创建线程,线程注册函数为SocketListener::threadStart;
  2. 执行runListener函数,如果socket监听到数据,则执行onDataAvailable函数进行处理;
  3. 调用logbuf->log(LogBuffer::log),这个函数很重要,新建一个LogBufferElement对象(用于保存log),调用mLogElements.insert将LogBufferElement加入list容器,实现log的保存。

Log使用

Log的buffer size

persist.logd.size.*
// 例如:persist.logd.size.main、persist.logd.size.radio、persist.logd.size.events、persist.logd.size.system、persist.logd.size.crash、persist.logd.size.stats、persist.logd.size.security、persist.logd.size.kernel

ro.logd.size.*
// 例如:ro.logd.size.main、ro.logd.size.radio、ro.logd.size.events、ro.logd.size.system、ro.logd.size.crash、ro.logd.size.stats、ro.logd.size.security、ro.logd.size.kernel

persist.logd.size
ro.logd.size
LOG_BUFFER_MIN_SIZE // 64K,条件是如果ro.config.low_ram是true,表示低内存手机
LOG_BUFFER_SIZE // 256K

logcat

源码位置system/core/logcat/下

执行adb logcat 即可查看log,通过logcat获取的log,并不完全是按照log分类来打印的,如在KERNEL log中可能存在MAIN log。logcat实现的大部分函数都在logcat/logcat.cpp文件中,其中__logcat函数是最重要的函数,其负责logcat 输入参数的解析以及log的处理。

logcat 最终读取log通过liblog/logd_reader.c 中的logdRead函数实现。此函数负责打开/dev/logdr,并通过socket获取log。

当同时启动6个线程分别获取main/kernel/system/crash/events等log时,由于启动的线程太多,然后就会有多个socket 与server 连接,可能会导致有log丢失。

logcat -h 查看帮助
logcat -b all 查看所有buffer的log,不加参数默认为main,system,crash
logcat -v threadtime 格式化时间方式
logcat -g 查看buffer size
logcat -G 2M 设置buffer size
logcat  -r 10240 -n 10  -f xxx.log  一个文件10M,加上xxx.log一共11个文件,达到最大个数后自动替换最老的文件
logcat -s 可以加tag,过滤日志
logcat --pid=xx 指定pid的日志输出
logcat -S 在输出中包含统计信息
logcat -c 清楚日志
logcat -p 查看当前的黑白名单
logcat -P 设置黑白名单

每次执行adb shell logcat命令后,系统会新起一个logcat进程,用来处理命令,父进程是adbd进程。adb shell logcat命令退出后,进程退出

logcat进程启动时入口在logcat_main.cpp#main()方法,其中核心android_logcat_run_command方法中调用__logcat方法来解析命令参数,最终通过Socket发送给logd处理等,例如clear命令会通过发送给logd的CommandListener类(logd.control线程)来处理。

目前用到了这些,后续持续更新

本文地址:https://blog.csdn.net/eatlemon/article/details/107158346