Android日志打印(11)

内核空间日志打印

日志保存

在linux内核中使用printk来实现日志打印输出且保存到/proc/kmsg,通过cat /proc/kmsg 或dmesg查看。
具体的打印级别如下:

#linux/kernel.h

#define KERN_EMERG  "<0>"    /* system is unusable          */  
#define KERN_ALERT  "<1>"    /* action must be taken immediately */  
#define KERN_CRIT  "<2>"    /* critical conditions          */  
#deinfe KERN_ERR    "<3>"    /* error conditions        */  
#deinfe KERN_WARNING    "<4>"    /* warning conditions          */  
#deinfe KERN_NOTICE "<5>"    /* normal but significant condition */  
#deinfe KERN_INFO  "<6>"    /* informational            */  
#deinfe KERN_DEBUG  "<7>"    /* debug-level messages        */ 
控制日志打印

在/proc/sys/kernel/printk保存了4个数字,分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最高允许设置的控制台日志级别、引导时默认的日志级别。
当printk的日志级别高于当前控制台日志级别时,信息就会在控制台上显示。我们可以通过echo 0 > /proc/sys/kernel/printk或者类似于echo 0 1 4 7 > /proc/sys/kernel/printk来关闭日志打印

用户空间日志打印

在Android用户空间中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统。


8090047.jpg
内核层面定义日志文件

在kernel的Logger驱动程序模块中(android\vendor\mstar\kernel\linaro\drivers\android\logger.c),定义了log_main、log_events、log_system和log_radio日志缓冲区,分别对应设备文件/dev/log/main、/dev/log/events、/dev/log/radio、/dev/log/system。

static int __init logger_init(void)
{
    ...
    ret = create_log(LOGGER_LOG_MAIN, 256*1024);
    ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
    ret = create_log(LOGGER_LOG_RADIO, 256*1024);
    ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
    ...
}

Logger可以读写/dev/log/main、/dev/log/events、/dev/log/radio、/dev/log/system等文件。

C/C++层面读写日志文件

1.在C/C++层定义了ALOG来实现日志打印,我们跟着ALOG来往下追溯源码:

system/core/include/log/log.h

#ifndef ALOGV
#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))

#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, fmt...) \
    __android_log_print(prio, tag, fmt)

2.__android_log_print()在logd_write.c中定义

#system/core/liblog/logd_write.c

int __android_log_print(int prio, const char *tag, const char *fmt, ...)
{
    return __android_log_write(prio, tag, buf);
}

int __android_log_write(int prio, const char *tag, const char *msg)
{
    return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}

3.又调用logd_write.c中的__android_log_buf_write()函数:

#system\core\liblog\logd_write.c
...
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
...
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
    ...
    /* XXX: This needs to go! */
    if ((bufID != LOG_ID_RADIO) &&
         (!strcmp(tag, "HTC_RIL") ||
        !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
        !strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" as the prefix */
        !strcmp(tag, "AT") ||
        !strcmp(tag, "GSM") ||
        !strcmp(tag, "STK") ||
        !strcmp(tag, "CDMA") ||
        !strcmp(tag, "PHONE") ||
        !strcmp(tag, "SMS"))) {
            bufID = LOG_ID_RADIO;
            /* Inform third party apps/ril/radio.. to use Rlog or RLOG */
            snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
            tag = tmp_tag;
    }
    ...
    return write_to_log(bufID, vec, 3);
}

5.再看write_to_log函数所指向的__write_to_log_init函数:

#\system\core\liblog\logd_write_kern.c

static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
    if (write_to_log == __write_to_log_init) {
        log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
        log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
        log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
        log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
        //指向__write_to_log_kernel函数
        write_to_log = __write_to_log_kernel;
        ...
    }
    return write_to_log(log_id, vec, nr);
}

6.__write_to_log_kernel函数调用log_writev

#\system\core\liblog\logd_write_kern.c

#define log_writev(filedes, vector, count) writev(filedes, vector, count)
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
    do {
        ret = log_writev(log_fd, vec, nr);
        if (ret < 0) {
            ret = -errno;
        }
    } while (ret == -EINTR);
}

7.最后log_writev()映射到具体的驱动层的writev()函数.把日志写到内核层面定义日志文件:/dev/log/main、/dev/log/events、/dev/log/radio、/dev/log/system。

8.关于C/C++层面如何打印日志:

# 方法一
1.定义TAG,引入log.h
#define LOG_TAG "lights"
#include <cutils/log.h>

2.直接用LOGV/LOGD/LOGI/LOGW/LOGE

#方法二
1.在Android.mk文件中加入LOCAL_LDLIBS := -llog 或 LOCAL_SHARED_LIBRARIES := \ libutils \ liblog \ libcutils

2.在CPP文件中加入
#include <android/log.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ProjectName", __VA_ARGS__);

3.直接用LOGD("XXXXX")
Java层面

1.Java层面通过Log类来实现日志的打印,具体的调用如下:

#frameworks/base/core/java/android/util/Log.java

 public static int e(String tag, String msg) {
        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    }
    // 这里定义的LOG_ID_MAIN和kernel中定义的对应
    /** @hide */ public static final int LOG_ID_MAIN = 0;
    /** @hide */ public static final int LOG_ID_RADIO = 1;
    /** @hide */ public static final int LOG_ID_EVENTS = 2;
    /** @hide */ public static final int LOG_ID_SYSTEM = 3;
    /** @hide */ public static final int LOG_ID_CRASH = 4;   

2.println_native函数在frameworks/base/core/jni/android_util_Log.cpp定义的

#frameworks/base/core/jni/android_util_Log.cpp

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

3.回到C/C++层面的第3步,发现Java层面和C/C++层面走到了一起

logcat 命令行工具

Logcat工具源代码位于system/core/logcat目录下,只有一个源代码文件logcat.cpp,编译后生成的可执行文件位于out/target/product/generic/system/bin目录下。
Logcat是一个命令行工具,可以把上面存储到/dev/log/main、/dev/log/events、/dev/log/radio、/dev/log/system的日志读取出来。
-c 清除(刷新)整个日志并退出。
-d 将日志转储到屏幕并退出。
-f <filename> 将日志消息输出写入 <filename>。默认值为 stdout。
-g 打印指定日志缓冲区的大小并退出。
-n <count> 将已旋转日志的最大数量设置为 <count>。默认值为 4。 需要使用 -r 选项。
-r <kbytes> 每输出 <kbytes> 时旋转日志文件。默认值为 16。需要使用 -f 选项。
-s 将默认过滤器规则设为静默式。
-v <format> 设置日志消息的输出格式。默认值为 brief 格式有关支持的格式列表

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,689评论 0 3
  • 1:InputChannel提供函数创建底层的Pipe对象 2: 1)客户端需要新建窗口 2)new ViewRo...
    自由人是工程师阅读 5,235评论 0 18
  • make menuconfig过程解析作者 codercjg 在 28 九月 2015, 5:27 下午 make...
    codercjg阅读 925评论 0 1
  • Linux print system linux中的调试方法有很多种,但我们最常用的也是最关键的调试工具应该就是使...
    Creator_Ly阅读 1,922评论 0 4
  • HTTP状态码分类 HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作...
    thorhill阅读 330评论 1 5