背景
通常我們會在量產的產品上,配置軟件僅打印少量日志,以提升產品的運行性能。同時我們要考慮預留方法讓軟件能夠擁有能力可以在燒錄版本后能夠通過修改默寫配置,打印更多日志。因為量產后的軟件通常開啟熔斷與加密,不能夠輕松替換動態庫,或者鏡像文件。
分析
一種比較簡單的方法是,通過讀取文件來重新配置。一般我們盡可能要讓自己動態庫獨立配置,而不是讓動態庫從可執行程序的main()函數入口讀取配置。通過可執行程序配置(設置回調函數,或者設置日志等級)會讓動態庫對可執行程序的耦合度增大。如果自己是乙方只提供.so,可執行程序入口由甲方提供,那么如上實現的方法就更麻煩了。為了方便各個動態庫單獨配置,我們可以利用 C語言的__attribute__ 特性來實現動態庫自己動態配置日志等級。參考實現方案如下。
方案
?my_log.c 參考代碼如下:
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/time.h>#define INPUT_LOG_LEVEL_FILE_PATH "/mnt/data/log-level"char my_printf_str[1024];
static int gLogLevel = MY_LOG_LEVEL_INFO;int my_log_print(int level, const char* fmt, ...){if (level > gLogLevel) return 0;va_list args;int val;struct timeval tv;gettimeofday(&tv, NULL);va_start( args, fmt );if(Callback_register.print != NULL) {vsprintf(BTF_printf_str, fmt, args);Callback_register.print("%d ", pthread_self());Callback_register.print("[%ld.%ld] ", tv.tv_sec, tv.tv_usec);val = Callback_register.print(my_printf_str);}else {printf("%d-", pthread_self());printf("[%ld.%ld] ", tv.tv_sec, tv.tv_usec);val = vprintf(fmt, args );}va_end(args);return val;
}int init_log_level(){char out = '0';FILE* fLogLevel = fopen(INPUT_LOG_LEVEL_FILE_PATH, "rb");do {if (NULL == fLogLevel) {printf("heyang: success log level file does not exist %s. Exiting.\n", INPUT_LOG_LEVEL_FILE_PATH);break;}if ((fread(&out, 1, 1, fLogLevel)) != 0) {rintf("read log level from file: %c\n", out);}fclose(fLogLevel);} while(0);int ret = atoi(&out);return ret > 0 ? ret : gLogLevel;
}static int __attribute__((constructor(101))) ___________________log_init() {gLogLevel = init_log_level();return 0;
}
my_log.h定義如下:
#define MY_LOG_LEVEL_FATAL 0
#define MY_LOG_LEVEL_ERROR 1
#define MY_LOG_LEVEL_WARNING 2
#define MY_LOG_LEVEL_INFO 3
#define MY_LOG_LEVEL_DEBUG 4
#define MY_LOG_LEVEL_VERBOSE 5int my_log_print(int level, const char* fmt, ...);
這樣就能能夠通過讀取?/mnt/data/log-level文件節寫的等級來動態配置了。
注意,加載這個.so的可執行程序需要配置好讀取文件的sepolicy權限(如果需要)。
測試
在代碼中使用:
my_log_print(MY_LOG_LEVEL_VERBOSE, "BTF: heyang print verbose level\n");my_log_print(MY_LOG_LEVEL_DEBUG, "BTF: heyang print debug level\n");my_log_print(MY_LOG_LEVEL_INFO, "BTF: heyang print info level\n");my_log_print(MY_LOG_LEVEL_WARNING, "BTF: heyang print warning level\n");my_log_print(MY_LOG_LEVEL_ERROR, "BTF: heyang print error level\n");my_log_print(MY_LOG_LEVEL_FATAL, "BTF: heyang print fatal level\n");
在文件節點寫入配置
touch?/mnt/data/log-levelecho 3 > /mnt/data/log-levelreset
重啟后運行可執行文件,在終端查看打印發現,so的 __attribute__((constructor(101))) 代碼成功執行。調用相關接口能夠打印對應等級的日志。
再換個配置試試:
echo 5 > /mnt/data/log-levelreset
然后測試能夠打印全部日志:
其他
?以上僅是通過printf()演示。實際使用,建議更換為slog或者其他的高級日志系統。