最近好幾天沒寫博客了,都感覺手生了,還是開始我們的NDK系列專題的文章
今天帶來的是一個用c++寫的日誌封裝,工作中一定會用到的
需求定位
我們一般用jni都是封裝一些庫供Android調用,其中有一項就是日誌打印,需要控制檯輸出,還需要文件輸出日誌,以便於查看客戶端使用情況,如果有bug,也可以快速定位
初步需求如下
- 供c++代碼調用
- 控制檯輸出
- 文件輸出(可控制文件大小)
- 可設置日誌等級
定義方法和變量
定義日誌輸出等級
enum {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERR = 1,
LOG_LEVEL_WARNING = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_DEBUG = 4
};
定義文件大小和日誌大小
#define LOG_TEXT_MAX_LENGTH (1024) // 單條日誌大小
#define LOG_FILE_MAX_SIZE (1024*1024*2) // 文件最大爲2MB
對於Android來說,需要引入log並定義日誌TAG
#include <android/log.h>
#define LOG_TAG "dds_log"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__ )
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__ )
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__ )
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
log.h定義幾個方法
/**
* 初始化日誌選項
* @param pFile
* @param filename
* @param logLevel
* @param printScreen
* @return
*/
int _LogInit(const char *pFile, 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需要傳入日誌路徑和名稱,已經日誌等級,需要Android調用,於是先來幾個jni的方法
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_c2_1base_Utils_test(JNIEnv *env, jclass type, jstring _str, jint a, jlong b) {
const char *str = env->GetStringUTFChars(_str, JNI_FALSE);
env->ReleaseStringUTFChars(_str, str);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_c2_1base_Utils_LogInit(JNIEnv *env, jobject instance, jstring logFilePath,
jstring logName, jint logfileLevel, jint logScreenLevel) {
if (ret != 0) {
const char *path = env->GetStringUTFChars(logFilePath, JNI_FALSE);
const char *name = env->GetStringUTFChars(logName, JNI_FALSE);
int fileLevel = logfileLevel;
int screenLevel = logScreenLevel;
ret = _LogInit(path, name, fileLevel, screenLevel);
env->ReleaseStringUTFChars(logFilePath, path);
env->ReleaseStringUTFChars(logName, name);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_c2_1base_Utils_log(JNIEnv *env, jclass type, jint _level, jstring _str) {
if (ret != 0) {
LOGE("log error! LogInit need");
return;
}
const char *str = env->GetStringUTFChars(_str, JNI_FALSE);
WriteTextLog(_level, str);
env->ReleaseStringUTFChars(_str, str);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_c2_1base_Utils_logClose(JNIEnv *env, jclass type) {
_LogClose();
ret = -1;
}
對應的java方法如下
public class Utils {
static {
System.loadLibrary("native-test");
}
public enum LogLevel {
LOG_LEVEL_NONE,
LOG_LEVEL_ERR,
LOG_LEVEL_WARNING,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG
}
public static native void test(String str, int a, long b);
// 日誌類初始化
public static native void LogInit(String logFilePath, String logName, int logfileLevel, int logScreenLevel);
// 這裏測試使用,所以將方法暴露出來,其實使用的時候是不需要暴露出來的
public static native void log(int logLevel, String content);
public static native void logClose();
}
編寫代碼
log.cpp詳細代碼
初始化
int _LogInit(const char *pFile, 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 (pFile != nullptr) {
g_logFilePath = std::string(pFile) + "/" + 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:
LOGD("%s", g_log_info);
sprintf(g_log_info, "%s [DEBUG] %s\n", timeStr, temBuf);
break;
case LOG_LEVEL_INFO:
LOGI("%s", g_log_info);
sprintf(g_log_info, "%s [INFO] %s\n", timeStr, temBuf);
break;
case LOG_LEVEL_WARNING:
LOGW("%s", g_log_info);
sprintf(g_log_info, "%s [WARN] %s\n", timeStr, temBuf);
break;
case LOG_LEVEL_ERR:
LOGE("%s", g_log_info);
sprintf(g_log_info, "%s [ERROR] %s\n", timeStr, temBuf);
break;
default:
LOGI("%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(), "we");// 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);
}
}
大功告成
具體代碼收錄到
https://github.com/ddssingsong/AnyNdk
c2_base測試代碼裏
謝謝關注