Android输出logs到文件

共两篇文章,第一篇讲述了如何在程序中输出Log信息,第二篇详细的分析了Log信息的输出机制。

下面是第一篇(转自:http://blog.163.com/binghaitao@126/blog/static/3383532520099309366435/)

1:在编译so文件的c或cpp文件之前中加入以下代码,就可以在android中的log显示日志内容
       #include <android/log.h>

       #define LOG_TAG "show infomation"

       #define LOGW(a ) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,a)

2:就可以在c或cpp中加入LOWG(str) 就可以在android中的log中显示打印的内容

3.这样写完以后,如果直接编译,就会报 __android_log_write 方法undefined.

怎么回事呢?关键是在设置编译选项上面。

在Android.mk文件里,可以指定一个LOCAL_LDLIBS的参数。如果不指定,那么编译的时候,只会引入默认的几个重要的lib,比如libc之类的。

如果要用log,那就要把 liblog给引进来。

网上很多的写法是 LOCAL_LDLIBS := -llog ,这在build static lib的时候没什么问题。如果是build shared lib,就会报个 cannot find -llog的错误。意思是找不到liblog.so这个库文件。

因此需要改成 LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog 才可以正常编译。

其中-L参数是指定了搜索lib的路径。

下面是一个android.mk的内容的例子:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := TestNdkNetwork

LOCAL_SRC_FILES := HttpConnection.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog

include $(BUILD_SHARED_LIBRARY)

 

 

下面是第二篇(转自http://blog.youkuaiyun.com/knock/archive/2010/04/21/5511255.aspx)


为了调试,必须要将log怎么打印的搞清楚,于是有了以下的分析。

我们通常在程序中插入LOGD(..),LOGE(..)之类的语句,但什么情况下可以查看这些打印消息呢?
首先,来到定义处:system/core/include/cutils/log.h,在开头就可以看到
#ifndef LOG_TAG
#define LOG_TAG NULL
#endif
所以程序中#include "log.h"之前要定义LOG_TAG,不然就为空.
再看LOGD的定义
#ifndef LOGD
#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
跟进
#ifndef LOG
#define LOG(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)

__android_log_print()是位于system/core/liblog/logd_write.c内
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
{
    va_list ap;
    char buf[LOG_BUF_SIZE];   

    va_start(ap, fmt);
    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
    va_end(ap);

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

    return write_to_log(log_id, vec, 3);
}
write_to_log定义如下
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) =
    __write_to_log_init;
查看一下
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#ifdef HAVE_PTHREADS
    pthread_mutex_lock(&log_init_lock);
#endif

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

        write_to_log = __write_to_log_kernel;

        if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
                log_fds[LOG_ID_EVENTS] < 0) {
            log_close(log_fds[LOG_ID_MAIN]);
            log_close(log_fds[LOG_ID_RADIO]);
            log_close(log_fds[LOG_ID_EVENTS]);
            log_fds[LOG_ID_MAIN] = -1;
            log_fds[LOG_ID_RADIO] = -1;
            log_fds[LOG_ID_EVENTS] = -1;
            write_to_log = __write_to_log_null;
        }
    }

#ifdef HAVE_PTHREADS
    pthread_mutex_unlock(&log_init_lock);
#endif

    return write_to_log(log_id, vec, nr);
}
这段的主要意思是打开/dev/log/main,/dev/log/radio,/dev/log/events三个设备都成功则将

write_to_log指向__write_to_log_kernel,否则指向__write_to_log_null。
下面就分别看看这两个
static int __write_to_log_null(log_id_t log_fd, struct iovec *vec, size_t nr)
{
    return -1;
}

static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
    ssize_t ret;
    int log_fd;

    if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
        log_fd = log_fds[(int)log_id];
    } else {
        return EBADF;
    }

    do {
        ret = log_writev(log_fd, vec, nr);
    } while (ret < 0 && errno == EINTR);

    return ret;
}
__write_to_log_null()什么也不做,表示丢弃log信息。__write_to_log_kernel会调用log_writev()

将log写进对应的设备(/dev/log/*).

为什么写进init.rc里由init来执行的程序不能输出log呢?下面再来探究一番。。
system/core/init/init.c中,
void service_start(struct service *svc)函数启动服务,有这么一句
        if (needs_console) {
            setsid();
            open_console();
        } else {
            zap_stdio();
        }
而这两个函数为:
static void zap_stdio(void)
{
    int fd;
    fd = open("/dev/null", O_RDWR);
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}

static void open_console()
{
    int fd;
    if ((fd = open(console_name, O_RDWR)) < 0) {
        fd = open("/dev/null", O_RDWR);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}
zap_stdio()比较狠,直接将STDIN,STDOUT,STDERR都干掉了,而open_console()则只是在/dev/console

不存在的情况下才干掉STDIN,STDOUT,STDERR,如果/dev/console存在,则将所有输入输出重定向到它


调用哪个取决于needs_console,
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
而svc->flags关于SVC_CONSOLE的部分来自于system/core/init/parser.c
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
       case K_console:
        svc->flags |= SVC_CONSOLE;
        break;

}
这也就是说如果init.rc中service部分有请求console,则可以打印到console。

但怎么样才能打印到系统的log中,可以使用logcat来查看呢?这就需要用到logwrapper。
system/core/logwrapper/logwrapper.c中,logwrapper先打开/dev/ptmx,查询到设备名后
fork()一个子进程并将STDOUT,STDERR定向到查询到的设备。
        // redirect stdout and stderr
        close(parent_ptty);
        dup2(child_ptty, 1);
        dup2(child_ptty, 2);
        close(child_ptty);
然后开始执行要运行的程序
child(argc - 1, &argv[1]);


总结:
系统中的程序中输出log一般是到/dev/log/下的三个设备中,可以用logcat查看。
对于init运行的程序则有两种方法查看到log信息:
1.添加/system/bin/logwrapper,可以用logcat查看,例如
service /system/bin/logwrapper /system/bin/rild
2.添加console,像sh一样直接输出到console
service console /system/bin/sh

<think>我们参考提供的引用内容,特别是引用[1]和引用[3],它们直接提到了如何将Logcat日志保存到文件。 根据引用[1],在Android Studio的Terminal中运行命令可以保存log。引用[3]则详细介绍了adb logcat命令的各种参数,其中提到使用`-f`选项可以将日志输出文件。 但是,引用[3]中提到“-f 参数执行不成功”,这可能是因为不同版本的adb或环境问题。因此,我们可以采用另一种方式:使用重定向符号(>)将标准输出重定向到文件。 步骤: 1. 打开Android Studio的Terminal(终端)窗口。 2. 确保设备已连接(通过USB或网络调试)。 3. 输入命令:`adb logcat > 文件路径`,例如:`adb logcat > D:\logcat.txt` 注意: - 这个命令会开始实时输出日志到文件,直到你按Ctrl+C停止。 - 如果你想清空之前的日志并重新开始记录,可以先运行`adb logcat -c`来清除旧的日志缓存,然后再运行上面的命令。 另外,引用[3]还提到`-d`选项可以将缓存的日志输出到屏幕并退出,所以也可以使用`adb logcat -d > 文件路径`来一次性获取当前缓存的所有日志并保存到文件,然后命令自动结束。 如果你需要按时间或者日志标签过滤,可以在logcat命令中加入参数,例如: - 按标签过滤:`adb logcat -s TAG_NAME > 文件路径` - 按优先级过滤:`adb logcat *:优先级(如W, E, I, D等)` 但是,请注意,在Terminal中运行adb命令需要adb在环境变量中,或者你使用Android Studio自带的adb(一般在Android SDK的platform-tools目录下)。如果Terminal中无法识别adb命令,你可能需要进入该目录执行,或者将adb路径添加到环境变量。 另外,引用[2]提到Logcat缓冲区溢出的问题,如果你需要长时间记录大量日志,可能需要增加缓冲区大小,但保存到文件的方式不受此影响,因为它是实时写入文件的。 因此,总结两种常用方法: 方法一(实时持续记录): `adb logcat > 文件路径` 方法二(一次性获取当前缓存): `adb logcat -d > 文件路径` 如果你需要停止实时记录,只需在Terminal中按Ctrl+C。 注意:在Windows系统中,文件路径可以是绝对路径,如`D:\logcat.txt`;在macOS或Linux中,可以是`/home/user/logcat.txt`等。 另外,为了避免日志过多导致文件过大,你可以使用`-d`选项定期获取,或者使用其他过滤条件来减少日志量。 现在,我们根据用户需求,给出具体步骤。</think>在 Android Studio 中将 Logcat 日志保存到文件,可通过以下两种常用方法实现: ### 方法一:使用 Terminal 命令(推荐) 1. 打开 Android Studio 的 **Terminal** 窗口(底部工具栏) 2. 输入命令: ```shell adb logcat -d > 文件路径/日志文件名.txt ``` **示例**(Windows): ```shell adb logcat -d > D:\logcat_logs\app_log.txt ``` **示例**(Mac/Linux): ```shell adb logcat -d > ~/Documents/logcat_logs/app_log.txt ``` - `-d` 参数:导出当前缓存日志后自动终止命令[^3] - 文件路径需提前创建(如 `D:\logcat_logs`) 3. **实时持续记录**(按需使用): ```shell adb logcat > 文件路径/实时日志.txt ``` 按 <kbd>Ctrl</kbd>+<kbd>C</kbd> 停止记录 --- ### 方法二:通过 Logcat 面板手动导出 1. 打开 **Logcat** 窗口(View > Tool Windows > Logcat) 2. 设置日志过滤条件(可选) 3. 点击顶部工具栏的 **Export** 图标(⬇️ 箭头) 4. 选择保存路径和文件名(默认格式为 `.txt`) --- ### 高级技巧 1. **按标签过滤保存**(如只保存 `MyApp` 标签): ```shell adb logcat -s MyApp > 过滤日志.txt ``` 2. **清空缓存后记录**(避免旧日志干扰): ```shell adb logcat -c && adb logcat -d > 纯净日志.txt ``` 3. **缓冲区选择**(如内核日志): ```shell adb logcat -b kernel > 内核日志.txt ``` --- ### 注意事项 - 确保设备已通过 USB 连接并启用 **USB 调试**[^2] - 若命令无效,检查 `adb` 环境变量或使用完整路径: ```shell # Windows 示例 C:\Android\sdk\platform-tools\adb.exe logcat -d > log.txt ``` - 大文件建议用 `>` 重定向(非 `-f` 参数),避免写入失败[^3] > 引用说明: > [^1]: 通过 Terminal 命令保存日志是解决 Logcat 显示不全的有效方法 > [^2]: 确保设备连接和缓冲区设置可防止日志丢失 > [^3]: `adb logcat` 参数详解(`-d`, `-s`, `-c`等)提供灵活的日志控制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值