前言
开发移动项目中,有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时只写入到文件,并不执行相应的控制台打印