背景
通常我们会在量产的产品上,配置软件仅打印少量日志,以提升产品的运行性能。同时我们要考虑预留方法让软件能够拥有能力可以在烧录版本后能够通过修改默写配置,打印更多日志。因为量产后的软件通常开启熔断与加密,不能够轻松替换动态库,或者镜像文件。
分析
一种比较简单的方法是,通过读取文件来重新配置。一般我们尽可能要让自己动态库独立配置,而不是让动态库从可执行程序的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或者其他的高级日志系统。