NDK 封装日志库 __android_log_print 输出 写到文件中记录

前言

开发移动项目中,有C++提供的so,调用so中函数时会输出打印,so中采用的是 __android_log_print 输出打印信息,因为要记录so中的问题,比如数据的采集正确性,偶尔崩溃的原因时需要通过日志信息来分析,所以就有了这个打算,将此输出写入到文件中去,当然写到文件中去后,也可以在控制台看到,当然发布时也可以不在控制台看到,所以下面列出几点需求

初步需求如下

  • 供c++代码调用
  • 控制台输出
  • 文件输出(可控制文件大小)
  • 可设置日志等级

定义方法和变量

定义日志输出等级

enum {
    LOG_LEVEL_NONE = 0,
    LOG_LEVEL_FATAL = 1,
    LOG_LEVEL_ERR = 2,
    LOG_LEVEL_WARNING = 3,
    LOG_LEVEL_INFO = 4,
    LOG_LEVEL_DEBUG = 5
};

越低级等级越高,例如Debug输出就是5,等级高说明所有打印都可以输出,根据个人需求来

定义文件大小和日志大小

#define LOG_TEXT_MAX_LENGTH        (1024)  //  单条日志大小
#define LOG_FILE_MAX_SIZE    (1024*1024*5) //  文件最大为5MB

对于Android移动端来说,需要引入 log 并定义日志 TAG

#include <android/log.h>
#include <errno.h>

#define  LOG_TAG    "AndroidLog"

#define  ALOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__ )
#define  ALOGI(...)  __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, __VA_ARGS__ )
#define  ALOGW(...)  __android_log_print(ANDROID_LOG_WARN,  LOG_TAG, __VA_ARGS__ )
#define  ALOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define  ALOGF(...)  __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)

编写代码

首先自己生成一个日志类,可以随便取名称,我取的就是CLog

CLog.h 定义如下几个方法:

 /**
     * 初始化日志选项
     * @param pFilePath  日志路径
     * @param filename   日志名称
     * @param logLevel   日志级别
     * @param printScreen 打印的级别
     * @return
     */
    int _LogInit(const char* pFilePath, const char* filename, int logLevel, int printScreen);

    /**
     * 写日志
     * @param level
     * @param strFormat
     * @param ...
     */
    void WriteTextLog(int level, const char* strFormat, ...);

    /**
     * 向文件中写入日志
     * @param level
     * @param log
     */
    void WriteTextLogBottom(int level, const char* log);

    /**
     * 关闭日志库
     */
    void _LogClose();

像第一条 _LogInit 需要传入日志路径和名称,以及日志等级,需要C++内部调用即可

CLog.cpp详细代码
初始化

int _LogInit(const char* pFilePath, const char* filename, int logLevel, int printScreen) {
    g_RollingPtr = 0;
    g_log_file_level = logLevel;
    g_log_screen_level = printScreen;
    if (filename != nullptr) {
        strcpy(LOG_FILE_NAME, filename);
    }
    if (pFilePath != nullptr) {
        g_logFilePath = std::string(pFilePath) + "/" + LOG_FILE_NAME;
    }
    else {
        g_logFilePath = LOG_FILE_NAME;
    }
    return 0;
}

打印日志

void WriteTextLog(int level, const char* strFormat, ...) {
    if (level > g_log_file_level && level > g_log_screen_level) {
        return;
    }
    time_t now;
    char timeStr[20];
    char temBuf[LOG_TEXT_MAX_LENGTH];

    time(&now);
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localtime(&now));

    va_list args;
    va_start(args, strFormat);
    vsnprintf(temBuf, sizeof(temBuf) - 1, strFormat, args);
    va_end(args);

    switch (level) {
    case LOG_LEVEL_DEBUG:
        ALOGD("%s", g_log_info);
        sprintf(g_log_info, "%s [DEBUG] %s\n", timeStr, temBuf);
        break;

    case LOG_LEVEL_INFO:
        ALOGI("%s", g_log_info);
        sprintf(g_log_info, "%s [INFO] %s\n", timeStr, temBuf);
        break;

    case LOG_LEVEL_WARNING:
        ALOGW("%s", g_log_info);
        sprintf(g_log_info, "%s [WARN] %s\n", timeStr, temBuf);
        break;

    case LOG_LEVEL_ERR:
        ALOGE("%s", g_log_info);
        sprintf(g_log_info, "%s [ERROR] %s\n", timeStr, temBuf);
        break;
    case LOG_LEVEL_FATAL:
        ALOGF("%s", g_log_info);
        sprintf(g_log_info, "%s [FATAL] %s\n", timeStr, temBuf);
        break;
    default:
        ALOGI("%s", g_log_info);
        sprintf(g_log_info, "%s [NONE] %s\n", timeStr, temBuf);
        break;
    }

    if (level <= g_log_file_level && !g_logFilePath.empty()) {
        WriteTextLogBottom(level, g_log_info);
    }
}

打印日志到文件

void WriteTextLogBottom(int level, const char* log) {
    if (level <= g_log_file_level) {
        FILE* fp;
        struct stat info {};
        if (stat(g_logFilePath.c_str(), &info) != 0) {
            g_RollingPtr = 0;
            fp = fopen(g_logFilePath.c_str(), "w");// create file
            if (fp == nullptr) {
                LOGE("%s, fopen(w) %s fail, err:%d", __func__, g_logFilePath.c_str(), errno);
                return;
            }
            fprintf(fp, "%s, stat fail create logfile, errno:%d", __func__, errno);
            fprintf(fp, "%s", log);
            fclose(fp);
            return;
        }

        if (info.st_size >= LOG_FILE_MAX_SIZE)// loop write
        {
            // 这里使用复写的方式,保证日志文件不会超过LOG_FILE_MAX_SIZE
            fp = fopen(g_logFilePath.c_str(), "r+");
            if (nullptr == fp) {
                LOGE("%s, fopen(r+) %s fail, size:%ld, err:%d", __func__, g_logFilePath.c_str(),
                    info.st_size, errno);
                return;
            }
            if (fseek(fp, g_RollingPtr, SEEK_SET) < 0) {
                fclose(fp);
                return;
            }
            g_RollingPtr += strlen(log);
            if (g_RollingPtr > info.st_size) {
                g_RollingPtr = 0;
            }
        }
        else {
            fp = fopen(g_logFilePath.c_str(), "a");
            if (fp == nullptr) {
                LOGE("%s, fopen(a) %s fail, size:%ld, err:%d", __func__, g_logFilePath.c_str(),
                    info.st_size, errno);
                return;
            }
        }
        fprintf(fp, "%s", log);
        fclose(fp);
    }
}

到这里就已经大功告成了! 当然还没完呢!

开发时的重新封装

就像 android/log.h 里面说的,当你进行开发的时候,可能需要很多的日志输出,但当你发布正式版本时,就应该尽量避免日志的输出。

如果使用上述方法,在发布正式版本的时候必须逐行注释掉日志,耗费时间。同时,如果在运行过程中出现问题,又必须重新输出日志,不利于开发。可以对上述代码再做一次封装:

#if 1
	#define  ALOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__ )
	#define  ALOGI(...)  __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, __VA_ARGS__ )
	#define  ALOGW(...)  __android_log_print(ANDROID_LOG_WARN,  LOG_TAG, __VA_ARGS__ )
	#define  ALOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
	#define  ALOGF(...)  __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#else
	#define ALOGD(...)
	#define ALOGI(...)
	#define ALOGW(...)
	#define ALOGE(...)
	#define ALOGF(...)
#endif

为1时调试输出到控制台并写入到文件,为0时只写入到文件,并不执行相应的控制台打印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fu_Lin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值