android log函数,Android Log系统

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

概要

基于logger设备驱动的Logger系统(Android 5.0之前不包含5.0):

Android的log包含两部分,内核中产生的,用户空间产生的。

内核空间产生的log通过Linux内核中的log系统进行处理,可以通过dmesg和/proc/kmsg进行访问。

用户空间的log系统我们称Logging系统将产生的log缓存到内核buffers中,整体架构如下:

05ba1a2b522c42060748de59d5c4b410.png

Logging系统包含如下部分:一个字符设备驱动以及内核中用来存储log内容的buffers区。

用来向Logging系统输入Log,以及查看Log的C、C++以及Java库。

一个用来查看log信息的程序-logcat。

Linux内核中有4个不同的buffers区,分别用来缓存不同类别的log。不同类别的log通过/dev/log目录下的不同设备节点进行访问。main:the main application log

events:for system event information

radio:for radio and phone-related information

system:a log for low-level system messages and debugging

logger设备驱动的实现在kernel/drivers/staging/android/logger.c h中

首先定义了四个设备节点/dev/log/main、/dev/log/radio、/dev/log/events、/dev/log/system.

这四个设备节点是作为misc设备通过misc_register(&log->misc);注册到misc设备系统中的。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* Log size must must be a power of two, and greater than

* (LOGGER_ENTRY_MAX_PAYLOAD + sizeof(struct logger_entry)).

*/

static int __init (char *log_name, int size)

{

int ret = 0;

struct logger_log *log;

unsigned char *buffer;

buffer = vmalloc(size); //分配log buffer用来存储log数据,不同的log类型buffer大小不一样。

if (buffer == NULL)

return -ENOMEM;

log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);

if (log == NULL) {

ret = -ENOMEM;

goto out_free_buffer;

}

log->buffer = buffer;

log->misc.minor = MISC_DYNAMIC_MINOR;

log->misc.name = kstrdup(log_name, GFP_KERNEL);

if (log->misc.name == NULL) {

ret = -ENOMEM;

goto out_free_log;

}

log->misc.fops = &logger_fops; //定义设备文件对应的file操作

log->misc.parent = NULL;

init_waitqueue_head(&log->wq);

INIT_LIST_HEAD(&log->readers);

mutex_init(&log->mutex);

log->w_off = 0;

log->head = 0;

log->size = size;

INIT_LIST_HEAD(&log->logs);

list_add_tail(&log->logs, &log_list);

/* finally, initialize the misc device for this log */

ret = misc_register(&log->misc); // 注册misc设备

if (unlikely(ret)) {

pr_err("failed to register misc device for log '%s'!n",

log->misc.name);

goto out_free_misc_name;

}

pr_info("created %luK log '%s'n",

(unsigned long) log->size >> 10, log->misc.name);

return 0;

out_free_misc_name:

kfree(log->misc.name);

out_free_log:

kfree(log);

out_free_buffer:

vfree(buffer);

return ret;

}

基于logd的Logger系统(Android5.0之后包含5.0):

最新的Android取消了logger设备驱动,后续通过用户空间的logd进程负责收集log数据。

Overview9.png

Android 的Logging系统在用户空间构建,logd进程负责收集缓存log,liblog通过socket负责和logd进程通信。

logd通过系统属性暴露接口,控制logd的属性。nametypedefaultdescriptionro.logd.auditdbooltrueEnable selinux audit daemon

ro.logd.auditd.dmesgbooltrueselinux audit messages sent to dmesg.

ro.logd.auditd.mainbooltrueselinux audit messages sent to main.

ro.logd.auditd.eventsbooltrueselinux audit messages sent to events.

persist.logd.securityboolfalseEnable security buffer.

ro.device_ownerboolfalseOverride persist.logd.security to false

ro.logd.kernelbool+svelte+Enable klogd daemon

ro.logd.statisticsbool+svelte+Enable logcat -S statistics.

ro.debuggablenumberif not “1”, logd.statistics & ro.logd.kernel default false.

logd.logpersistd.enableboolautoSafe to start logpersist daemon service

logd.logpersistdstringpersistEnable logpersist daemon, “logcatd” turns on logcat -f in logd context. Responds to logcatd, clear and stop.

logd.logpersistd.bufferpersistlogpersistd buffers to collect

logd.logpersistd.sizepersistlogpersistd size in MB

persist.logd.logpersistdstringEnable logpersist daemon, “logcatd” turns on logcat -f in logd context.

persist.logd.logpersistd.bufferalllogpersistd buffers to collect

persist.logd.logpersistd.size256logpersistd size in MB

persist.logd.sizenumberroGlobal default size of the buffer for all log ids at initial startup, at runtime use: logcat -b all -G

ro.logd.sizenumbersveltedefault for persist.logd.size. Larger platform default sizes than 256KB are known to not scale well under log spam pressure. Address the spam first, resist increasing the log buffer.

persist.logd.size.numberroSize of the buffer for log

ro.logd.size.numbersveltedefault for persist.logd.size.

ro.config.low_ramboolfalseif true, logd.statistics, ro.logd.kernel default false, logd.size 64K instead of 256K.

persist.logd.filterstringPruning filter to optimize content. At runtime use: logcat -P ““

ro.logd.filterstring“~! ~1000/!”default for persist.logd.filter.This default means to prune the oldest entries of chattiest UID, and the chattiest PID of system (1000, or AID_SYSTEM).

persist.logd.timestampstringroThe recording timestamp source. “m[onotonic]” is the only supported key character, otherwise realtime.

ro.logd.timestampstringrealtime default for persist.logd.timestamp

log.tagstringpersist The global logging level, VERBOSE,DEBUG, INFO, WARN, ERROR, ASSERT or SILENT. Only the first character is the key character.

persist.log.tagstringbuild default for log.tag

log.tag.stringpersist The specific logging level.

persist.log.tag.stringbuild default for log.tag.

logd创建了3个socket通道用于通信分别是:/dev/socket/logd

/dev/socket/logdr

/dev/socket/logdw

1.CommandListener-logd

e8a316d1e060b636e9b3f8aac2b86057.png

整体流程大概如下:

首先CommandListener构建时打开设备节点/dev/socket/logd,并监听Client的请求,同时通过registerCmd设置能够处理的请求。

registerCmd的操作是将Server能处理的请求加入到mCommands队列中。

当接收到Client请求时,构造一个SocketClient并加入到队列mClients中。

同时CommandListener对Client发送来的Command请求,到mCommands队列中去查找,当查找到相应的FrameworkCommnad时,使用其runCommand函数执行请求。

在CommandListener的构造函数中可以看到其设置并可以处理的Command。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15CommandListener::CommandListener(LogBuffer* buf, LogReader* /*reader*/,

LogListener* /*swl*/)

: FrameworkListener(getLogSocket()) {

// registerCmd(new ShutdownCmd(buf, writer, swl));

registerCmd(new ClearCmd(buf));

registerCmd(new GetBufSizeCmd(buf));

registerCmd(new SetBufSizeCmd(buf));

registerCmd(new GetBufSizeUsedCmd(buf));

registerCmd(new GetStatisticsCmd(buf));

registerCmd(new SetPruneListCmd(buf));

registerCmd(new GetPruneListCmd(buf));

registerCmd(new GetEventTagCmd(buf));

registerCmd(new ReinitCmd());

registerCmd(new ExitCmd(this));

}

2.log缓存Buffer

logd中通过LogBuffer缓存log消息。LogBuffer结构如下:

98aae6da4185271d0dc9ba91fbe37092.png

所有发送到logd的log信息都构建成了LogBufferElement元素存储在列表mLogElements中。

logd/LogBuffer.cpp1

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// assumes LogBuffer::wrlock() held, owns elem, look after garbage collection

void LogBuffer::log(LogBufferElement* elem) {

// cap on how far back we will sort in-place, otherwise append

static uint32_t too_far_back = 5; // five seconds

// Insert elements in time sorted order if possible

// NB: if end is region locked, place element at end of list

LogBufferElementCollection::iterator it = mLogElements.end();

LogBufferElementCollection::iterator last = it;

if (__predict_true(it != mLogElements.begin())) --it;

if (__predict_false(it == mLogElements.begin()) ||

__predict_true((*it)->getRealTime() <= elem->getRealTime()) ||

__predict_false((((*it)->getRealTime().tv_sec - too_far_back) >

elem->getRealTime().tv_sec) &&

(elem->getLogId() != LOG_ID_KERNEL) &&

((*it)->getLogId() != LOG_ID_KERNEL))) {

mLogElements.push_back(elem); //将LogBufferElement元素存入到mLogElemnts中

} else {

......

if (end_always || (end_set && (end > (*it)->getRealTime()))) {

mLogElements.push_back(elem); //将LogBufferElement元素存入到mLogElements中

} else {

// should be short as timestamps are localized near end()

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); //将LogBufferElement元素存入到mLogElements中

}

LogTimeEntry::unlock();

}

stats.add(elem);

maybePrune(elem->getLogId());

}

3.从logd读取log

客户端如logcat通过/dev/sock/logdr socket文件和logd交互读取log,logd读取侧的结构如下:

df0613e2848789a30ee06f3defa38910.png

LogReader构建时开始监听socket /dev/sock/logdr的数据,当接收到读取log的请求时,调用onDataAvailable()函数处理请求。

在onDataAvailable()函数中会构造FlushCommand对象并通过runSocketCommand()函数向Client发送其需要的log。

而在runSocketCommand中其构造了LogTimeEntry,并用startReader_Locked()函数启动了一个线程,将LogBuffer中的数据发送到Client中。

在这之后其他的Client能再次通过/dev/sock/logdr请求log数据。

logd/LogTimes.cpp1

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

72void* LogTimeEntry::threadStart(void* obj) {

prctl(PR_SET_NAME, "logd.reader.per");

LogTimeEntry* me = reinterpret_cast(obj);

pthread_cleanup_push(threadStop, obj);

SocketClient* client = me->mClient;

if (!client) {

me->error();

return nullptr;

}

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->threadRunning && !me->isError_Locked()) {

if (me->mTimeout.tv_sec || me->mTimeout.tv_nsec) {

if (pthread_cond_timedwait(&me->threadTriggeredCondition,

&timesLock, &me->mTimeout) == ETIMEDOUT) {

me->mTimeout.tv_sec = 0;

me->mTimeout.tv_nsec = 0;

}

if (!me->threadRunning || me->isError_Locked()) {

break;

}

}

unlock();

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

wrlock();

if (start == LogBufferElement::FLUSH_ERROR) {

me->error_Locked();

break;

}

me->mStart = start + log_time(0, 1);

if (me->mNonBlock || !me->threadRunning || me->isError_Locked()) {

break;

}

me->cleanSkip_Locked();

if (!me->mTimeout.tv_sec && !me->mTimeout.tv_nsec) {

pthread_cond_wait(&me->threadTriggeredCondition, &timesLock);

}

}

unlock();

pthread_cleanup_pop(true);

return nullptr;

}

4.log写入logd

在logd侧写入log比较简单,logd中接收Client传递过来的log数据后执行onDataAvilable(),并将其写入到LogBuffer中。

edb3a32e485a9d0219f13eee1e656a96.png

liblog

基于logger设备驱动的Logger系统:

基于logd的Logger系统:

liblog中通过struct android_log_transport_write抽象log写入设备。

liblog/logger.h1

2

3

4

5

6

7

8

9

10

11

12

13struct 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 */

/* write log to transport, returns number of bytes propagated, or -errno */

int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec,

size_t nr);

};

目前liblog提供如下几类的android_log_transport_write:fakeLoggerWrite:liblog/fake_writer.c

localLoggerWrite:liblog/local_logger.c

logdLoggerWrite:liblog/logd_writer.c

pmsgLoggerWrite:liblog/pmsg_writer.c

stderrLoggerWrite:liblog/stderr_write.c

statsdLoggerWrite:libstats/statsd_writer.c

liblog的使用

参考/system/core/liblog/README我们使用liblog进行log输出主要做下面几方面:定义LOG_TAG

include 头文件

使用log函数,打印log。1

2

3

4

5

6

7

8

9

10#include

ALOG(android_priority, tag, format, ...)

IF_ALOG(android_priority, tag)

LOG_PRI(priority, tag, format, ...)

LOG_PRI_VA(priority, tag, format, args)

#define LOG_TAG NULL

ALOGV(format, ...)

......

C++ Style的Logging

C++中通过std::cout<

<

我们通过如下形式使用:1

2

3#include

LOG(INFO) << "Some text; " << some_value;

并且在Android.mk或Android.bp中申明链接使用libase库。1

2

3

4shared_libs: [

......

"libbase",

],

标准输出、标准错误

在Android有一些应用程序的日志输出是通过printf之类的标准函数输出的,这类log是无法记录到Logging系统的。

主要是由于init进程会把0,1,2三个fd指向到/dev/null,而其他进程都是由init fork出来的,所以标准输出、标准错误输出都会继承自父进程,默认也都是不打印出来的。

main.cpp(main)->init.cpp(main)->log.cpp(InitKernelLogging)

system/core/init/log.cpp1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16void InitKernelLogging(char* argv[]){

// Make stdin/stdout/stderr all point to /dev/null.

int fd = open("/sys/fs/selinux/null", O_RDWR);

if (fd == -1) {

int saved_errno = errno;

android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);

errno = saved_errno;

PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";

}

dup2(fd, 0);

dup2(fd, 1);

dup2(fd, 2);

if (fd > 2) close(fd);

android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);

}

标准输出、标准错误log输出

android中提供了logwrapper程序用来重定向标准输出、标准错误的log输出,重定向的log可以使用logcat查看.

源代码实现在system/core/logwrapper中。

使用logwrapper命令来执行第三方应用程序,logwrapper命令可以把第三方应用程序的标准输出重定向到logcat的日志系统中去(缺省级别为LOG_INFO,标签为应用程序名)。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15PD1829:/ # logwrapper -h

logwrapper: invalid option -- h

Usage: logwrapper [-a] [-d] [-k] BINARY [ARGS ...]

Forks and executes BINARY ARGS, redirecting stdout and stderr to

the Android logging system. Tag is set to BINARY, priority is

always LOG_INFO.

-a: Causes logwrapper to do abbreviated logging.

This logs up to the first 4K and last 4K of the command

being run, and logs the output when the command exits

-d: Causes logwrapper to SIGSEGV when BINARY terminates

fault address is set to the status of wait()

-k: Causes logwrapper to log to the kernel log instead of

the Android system log

我们写一个程序通过printf向标准输出打印helloworld,直接执行时无法看到adb logcat中有相应的log。1

2

3

4

5

6

7

8

9

10

11

12

13#include

#include

#include

int main(int argc, char* const argv[])

{

while (1) {

void *p = malloc(100);

printf("helloworld from testprintfn");

sleep(10);

free(p);

}

}

当我们通过logwrapper对log进行重定向时就能通过adb logcat看到输出的hellworld了。1130|PD1829:/ # logwrapper testprintf109-11 12:07:06.375 I/testprintf(12056): helloworld from testprintf

/dev/ptmx设备

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值