当前位置: 移动技术网 > 移动技术>移动开发>Android > 基于Android P Log系统学习笔记

基于Android P Log系统学习笔记

2020年07月09日  | 移动技术网移动技术  | 我要评论

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网