NDK 封裝日誌庫 寫到文件

最近好幾天沒寫博客了,都感覺手生了,還是開始我們的NDK系列專題的文章
今天帶來的是一個用c++寫的日誌封裝,工作中一定會用到的

需求定位

我們一般用jni都是封裝一些庫供Android調用,其中有一項就是日誌打印,需要控制檯輸出,還需要文件輸出日誌,以便於查看客戶端使用情況,如果有bug,也可以快速定位

初步需求如下

  1. 供c++代碼調用
  2. 控制檯輸出
  3. 文件輸出(可控制文件大小)
  4. 可設置日誌等級

定義方法和變量

定義日誌輸出等級

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測試代碼裏

謝謝關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章