0. 简介

对于C++,无论是大学生还是算法工程师都是非常需要学习并使用的一门语言,而C++不像python、rust一样简单好用。不单单是在嵌套复杂代码后的逻辑还是各种类和堆栈的管理,都是非常头疼的问题。一般来说对于LOG类很多都是使用GLOG、而堆栈跟踪一般是基于GDB。而这里我们将开拓大家眼界,从另一个角度来说一些小而美的东西。这里整合一些网上非常好的博客,并结合自己的一些理解给大家整理一个非常通用的工具,也欢迎各位关注引用文章的博主。

1. RLOG

在现代软件开发中,日志记录系统是不可或缺的一部分。它不仅可以帮助开发人员在应用程序中定位和解决问题,还可以用于监控、性能分析、安全审计等方面。本文将介绍日志记录系统的基本概念、重要性以及如何构建一个高效的日志记录系统。

1.1 构建日志记录系统的关键步骤

  • 日志级别与分类:日志级别包括调试(Debug)、信息(Info)、警告(Warning)、错误(Error)等。不同级别的日志用于不同目的,例如调试时使用调试日志来追踪代码执行,而错误日志用于记录严重的问题。选择合适的日志级别可以避免日志信息过于冗杂或过于稀少。
  • 日志格式与结构:一个良好的日志格式应包括时间戳、日志级别、模块/组件名以及具体的日志消息。统一的格式使得日志易于阅读和分析。例如:[时间戳] [日志级别] [模块名] - 日志消息
  • 异步日志写入:为避免阻塞应用程序的正常执行,可以采用异步日志写入方式。日志消息被缓冲并在适当的时机写入磁盘,从而提高应用程序的性能。
  • 日志存储与滚动:考虑使用滚动策略,定期归档或删除旧的日志文件,以免日志文件无限增大。选择适当的存储方式,如本地文件、数据库或云存储。
  • 上下文信息:除了基本的日志信息外,还可以在日志中添加上下文信息,如用户ID、请求ID、会话ID等。这些信息有助于在复杂的分布式系统中跟踪请求流程。
    敏感信息与安全:避免将敏感信息(如密码、API密钥)写入日志。同时,实施权限控制,限制对日志文件的访问,确保敏感信息不被滥用。
  • 日志分析与可视化:利用日志分析工具或平台,对日志进行聚合、搜索和可视化分析。这有助于发现模式、趋势以及潜在问题。

1.2 Rlog组件

Rlog作为一款高性能的纯C语言日志组件,为开发人员提供了一种轻松、灵活且可定制的日志记录解决方案。对于Rlog而言

  • 它支持用户自定义输出端点(例如:串口终端、网络中断、Flash…),输出端点以插件形式自定义扩展。
  • 日志内容可包含日志等级、时间戳、行号,函数信息,文件信息;
  • 支持多种操作系统(RT-Thread、Linux…),也支持裸机平台;
  • 日志输出支持:printf原始格式,日志等级输出,hexdump输出;
├── example
│   ├── rlog_linux_adapter.c        /* linux环境下的适配接口 */
│   └── rlog_rtt_adapter.c          /* rt-thread环境下的适配接口 */
├── include/
│   ├── rlog_adapter.h              /* rlog适配描述 */
│   └── rlog.h                      /* rlog对外接口 */
├── main.c                          /* rlog的测试样例 */
├── Makefile                        /* linux环境rlog构建Makefile */
├── plug-in                         /* 输出端点插件的存放路径 */
├── SConscript                      /* rt-thread环境rlog构建脚本 */
└── src
    ├── rlog.c                      /* rlog核心代码 */
    ├── rlog_def.h                  /* rlog核心代码使用的定义 */
    └── rlog_utils.c                /* rlog使用的C库接口 */

1.3 灵活配置与使用

1.3.1 静态配置:
  1. 静态配置采用宏定义的方式,用户可直接修改rlog_adapter.h头文件的宏定义,就可以修正相关配置;
  2. 静态配置的管控权限比动态配置的高,例如:静态配置设置日志输出总开关为关闭,动态配置设置日志输出使能,日志已经无法输出。
    配置描述如下:
/* Enable log output */
#define RLOG_OUTPUT_ENABLE              1
/* Set log output level, rang: from RLOG_LEVEL_ASSERT to RLOG_LEVEL_VERBOSE */
#define RLOG_OUTPUT_LEVEL               RLOG_LEVEL_VERBOSE
/* Enable log color */
#define RLOG_COLOUR_ENABLE              1
/* Enable log color */
#define RLOG_TIME_ENABLE                1
/* Support log include directory */
#define RLOG_DIRECTORY_ENABLE           1
/* Support log include funtiong name */
#define RLOG_FUNCTION_ENABLE            1
/* Support log include line number*/
#define RLOG_LINE_ENABLE                1
/* Buffer size for every line's log */
#define RLOG_LINE_BUFF_LEN              128
/* Output line number max length */
#define RLOG_LINE_NUM_SIZE              5
/* Output newline sign */
#define RLOG_NEWLINE_SIGN               "\r\n"
/* Enable assert check */
#define RLOG_ASSERT_ENABLE              1
/* Log function. default FDB_PRINT macro is printf() */
#define RLOG_PRINT(...)                 printf(__VA_ARGS__)

1.3.2 动态配置:
  1. 动态配置采用接口的方式,用户通过调用rlog.h头文件提供的接口设置;
  2. 动态配置的管控权限比静态配置的低,例如:静态配置设置日志输出总开关为关闭,动态配置设置日志输出使能,日志已经无法输出。
**
 * RLog output enable
 * 
 * @param enable true: enable output, false: disable output 
 */
void rlog_enable(bool enable);
/**
 * RLog output color enable
 * 
 * @param enable true: enable output color, false: disable output color
 */
void rlog_color_enable(bool enable);
/**
 * RLog level output format setting
 * 
 * @param level  log level
 * @param format log format
 */
void rlog_level_fmt_set(rlog_lvl_t level, int format);
/**
 * RLog level output format setting
 * 
 * @param level  log level
 * @param format log format
 * 
 * @return result true: supported format, false: unsupported format
 */
bool rlog_level_fmt_get(rlog_lvl_t level, int format);
/**
 * RLog filter the content of the log level
 * 
 * @param level  log level
 */
void rlog_level_filter_set(rlog_lvl_t level);

1.3.3 Rlog适配

不同平台的适配方式不同,所以为rlog核心层提供了统一的接口,适配接口如下:

static pthread_mutex_t mutex;

void rlog_lock(void)
{
    pthread_mutex_lock(&mutex);
}

void rlog_unlock(void)
{
    pthread_mutex_unlock(&mutex);
}

char *rlog_get_time(void)
{
#define TIME_STR_SIZE       32
    static char time_str[TIME_STR_SIZE] = {0};
    time_t tmp;
    struct tm *timp;

    time(&tmp);   
    timp = localtime(&tmp);

    memset(time_str, 0, TIME_STR_SIZE);
    rlog_snprintf(time_str, TIME_STR_SIZE, "%04d-%02d-%02d %02d:%02d:%02d", 
                                (1900 + timp->tm_year), (1 + timp->tm_mon), timp->tm_mday,
                                timp->tm_hour, timp->tm_min, timp->tm_sec);

    return time_str;
}

void rlog_output(const char *log, uint16_t len)
{
    RLOG_PRINT("%.*s", len, log);
}

void rlog_adapter_init(void)
{
    pthread_mutex_init(&mutex, NULL);
}

void rlog_adapter_deinit(void)
{
    pthread_mutex_destroy(&mutex);
}
1.3.4 Rlog 使用

日志记录方式

//level:日志级别,log:日志内容  
rlog.log(level, log);
//自定义日志参数信息
const debugObj={
        info:'Debug信息',
        module:'视图1',
        debug:{'cardData':cardData},
        codeline:67,
        file:'view_overview.jsx'
}
rlog.log('info',"可以只输入一条文字信息");

rlog.info('可以直接按级别输出信息');

rlog.log('debug',debugObj);

//预设日志方法

rlog.getReactDrawTime('视图');   //获取react模块渲染时间

rlog.event(e);      //输出一条event日志

2. Backward-cpp

C/C++编程的同学经常会遇到程序出现(Segmentation fault (core dumped))段错误,一般的解决方法就是使用gdb的backtrace来进行检查。而Backward 会将堆栈信息打印出来,并且源码本质上只有backward.hpp文件,集成到自己的程序中非常方便,如果加入backward.cpp文件一起编译,则自己代码中不需要调用Backward-cpp中的函数,非常方便。

2.1 编译Backward-cpp

git clone https://github.com/bombela/backward-cpp.git

我们可以看到主文件就只有backward.hppbackward.cpp文件,测试文件有下面这些:

.test
├── rectrace.cpp
├── select_signals.cpp
├── stacktrace.cpp
├── suicide.cpp
├── test.cpp
├── test.hpp
└── _test_main.cpp

然后我们就安装编译

sudo apt-get install libdw-dev
sudo apt-get install binutils-dev
sudo apt-get install libdwarf-dev

cd backward-cpp
mkdir build && cd build
 cmake ..
 make -j16
  ./test_stacktrace

这样我们就可以看到堆栈的信息了

-- running test case: minitrace
Stack trace (most recent call last):
#9    Object "", at 0xffffffffffffffff, in 
#8    Object "/home/pony/codes/download/backward-cpp/build/test_stacktrace", at 0x55c82d46edad, in _start
#7    Source "../csu/libc-start.c", line 308, in __libc_start_main
#6    Source "/home/pony/codes/download/backward-cpp/test/_test_main.cpp", line 227, in main
        224:     }
        225: 
        226:     total_cnt += 1;
      > 227:     if (run_test(test)) {
        228:       printf("-- test case success: %s\n", test.name);
        229:       success_cnt += 1;
        230:     } else {
#5    Source "/home/pony/codes/download/backward-cpp/test/_test_main.cpp", line 140, in run_test
        138:   pid_t child_pid = fork();
        139:   if (child_pid == 0) {
      > 140:     exit(static_cast<int>(test.run()));
        141:   }
        142:   if (child_pid == -1) {
        143:     error(EXIT_FAILURE, 0, "unable to fork");
#4    Source "/home/pony/codes/download/backward-cpp/test/test.hpp", line 92, in run
         90:   TestStatus run() {
         91:     try {
      >  92:       do_test();
         93:       return SUCCESS;
         94:     } catch (const AssertFailedError &e) {
         95:       printf("!! %s\n", e.what());
#3    Source "/home/pony/codes/download/backward-cpp/test/stacktrace.cpp", line 37, in do_test
         34:   Printer printer;
         35: 
         36:   StackTrace st;
      >  37:   collect_trace(st);
         38: 
         39:   printer.print(st, std::cout);
         40: }
#2    Source "/home/pony/codes/download/backward-cpp/test/stacktrace.cpp", line 31, in collect_trace
         29: using namespace backward;
         30: 
      >  31: void collect_trace(StackTrace &st) { st.load_here(); }
         32: 
         33: TEST(minitrace) {
         34:   Printer printer;
#1    Source "/home/pony/codes/download/backward-cpp/backward.hpp", line 879, in load_here
        876:       return 0;
        877:     }
        878:     _stacktrace.resize(depth);
      > 879:     size_t trace_cnt = details::unwind(callback(*this), depth);
        880:     _stacktrace.resize(trace_cnt);
        881:     skip_n_firsts(0);
        882:     return size();
#0    Source "/home/pony/codes/download/backward-cpp/backward.hpp", line 861, in unwind<backward::StackTraceImpl<backward::system_tag::linux_tag>::callback>
        859: template <typename F> size_t unwind(F f, size_t depth) {
        860:   Unwinder<F> unwinder;
      > 861:   return unwinder(f, depth);
        862: }
        863: 
        864: } // namespace details
-- test case success: minitrace
-- running test case: smalltrace
Stack trace (most recent call last):
#12   Object "", at 0xffffffffffffffff, in 
#11   Object "/home/pony/codes/download/backward-cpp/build/test_stacktrace", at 0x55c82d46edad, in _start
#10   Source "../csu/libc-start.c", line 308, in __libc_start_main
#9    Source "/home/pony/codes/download/backward-cpp/test/_test_main.cpp", line 227, in main
        224:     }
        225: 
        226:     total_cnt += 1;
      > 227:     if (run_test(test)) {
        228:       printf("-- test case success: %s\n", test.name);
        229:       success_cnt += 1;
        230:     } else {
#8    Source "/home/pony/codes/download/backward-cpp/test/_test_main.cpp", line 140, in run_test
        138:   pid_t child_pid = fork();
        139:   if (child_pid == 0) {
      > 140:     exit(static_cast<int>(test.run()));
        141:   }
        142:   if (child_pid == -1) {
        143:     error(EXIT_FAILURE, 0, "unable to fork");
#7    Source "/home/pony/codes/download/backward-cpp/test/test.hpp", line 92, in run
         90:   TestStatus run() {
         91:     try {
      >  92:       do_test();
         93:       return SUCCESS;
         94:     } catch (const AssertFailedError &e) {
         95:       printf("!! %s\n", e.what());
#6    Source "/home/qiancj/codes/download/backward-cpp/test/stacktrace.cpp", line 54, in do_test
         51:   Printer printer;
         52: 
         53:   StackTrace st;
      >  54:   a(st);
         55: 
         56:   printer.print(st, std::cout);
         57: }
#5    Source "/home/qiancj/codes/download/backward-cpp/test/stacktrace.cpp", line 48, in a
         46: void b(StackTrace &st) { return c(st); }
         47: 
      >  48: NOINLINE void a(StackTrace &st) { return b(st); }
         49: 
         50: TEST(smalltrace) {
         51:   Printer printer;
#4    Source "/home/qiancj/codes/download/backward-cpp/test/stacktrace.cpp", line 46, in b
         44: void c(StackTrace &st) { return d(st); }
         45: 
      >  46: void b(StackTrace &st) { return c(st); }
         47: 
         48: NOINLINE void a(StackTrace &st) { return b(st); }
#3    Source "/home/qiancj/codes/download/backward-cpp/test/stacktrace.cpp", line 44, in c
         42: void d(StackTrace &st) { st.load_here(); }
         43: 
      >  44: void c(StackTrace &st) { return d(st); }
         45: 
         46: void b(StackTrace &st) { return c(st); }
#2    Source "/home/qiancj/codes/download/backward-cpp/test/stacktrace.cpp", line 42, in d
         39:   printer.print(st, std::cout);
         40: }
         41: 
      >  42: void d(StackTrace &st) { st.load_here(); }
         43: 
         44: void c(StackTrace &st) { return d(st); }
#1    Source "/home/qiancj/codes/download/backward-cpp/backward.hpp", line 879, in load_here
        876:       return 0;
        877:     }
        878:     _stacktrace.resize(depth);
      > 879:     size_t trace_cnt = details::unwind(callback(*this), depth);
        880:     _stacktrace.resize(trace_cnt);
        881:     skip_n_firsts(0);
        882:     return size();
#0    Source "/home/qiancj/codes/download/backward-cpp/backward.hpp", line 861, in unwind<backward::StackTraceImpl<backward::system_tag::linux_tag>::callback>
        859: template <typename F> size_t unwind(F f, size_t depth) {
        860:   Unwinder<F> unwinder;
      > 861:   return unwinder(f, depth);
        862: }
        863: 
        864: } // namespace detail
        ...............
-- test case success: smalltrace
-- tests passing: 2/2 (100%)

1.2 集成Backward-cpp

除了.hpp以外,如果希望 Backward 自动打印最常见的致命错误(段错误、中止、未处理的异常等)的堆栈跟踪,只需将“backward.cpp”的副本添加到项目中,一起编译即可。

#include<stdio.h>
#include<stdlib.h>
#define BACKWARD_HAS_DW 1
#include "backward.hpp"
namespace backward{
    backward::SignalHandling sh;
}

int main(){
    char *c = "hello world";
    c[1] = 'H';
}

运行程序

Stack trace (most recent call last):
#3    Object "", at 0xffffffffffffffff, in 
#2    Object "/home/pony/codes/test/backward/randy", at 0x562025c8eecd, in _start
#1    Source "../csu/libc-start.c", line 308, in __libc_start_main [0x7f5ff0c6d082]
#0    Source "/home/pony/codes/test/backward/test_backward.cpp", line 11, in main [0x562025c8efa4]
          9: int main(){
         10:     char *c = "hello world";
      >  11:     c[1] = 'H';
         12: }
Segmentation fault (Invalid permissions for mapped object [0x562025c9d0e8])
Segmentation fault (core dumped)

3. SourceTrail

Sourcetrail 是一个交互式源资源管理器,它通过索引代码并收集有关其结构的数据来简化现有源代码中的导航。

说白了,可以对一个代码仓进行结构分析,有哪些文件、命名空间、类、结构体函数等,对于类可以方便地查看类之间的继承关系,类的成员变量及成员函数等,对于分析新上手的项目,非常适用。Sourcetrail 提供了一个由三个交互式视图组成的简单界面,每个视图在帮助您获取所需信息方面发挥着关键作用:

  • 搜索:使用搜索字段在源代码中快速查找和选择索引符号。自动完成框将立即提供整个代码库的所有匹配结果的概述。
  • 图表:图表显示源代码的结构。它专注于当前选定的符号,并直接显示其他符号的所有传入和传出依赖项。
  • 代码:“代码”视图在代码段列表中显示当前选定元件的所有源位置。单击其他源位置可以更改选择并深入挖掘。

3.2 代码分析测试

3.2.1 新建项目

3.2.2 设置项目名称和项目存储位置,然后点击 Add Source Group,添加新的源码组

3.2.3 设置新的源码组的语言及配置模板,一般选空的模板即可

3.2.4 添加空的配置模板之后,开始配置:
  • 语言版本
  • 编译平台
  • 源码路径
  • 需要排除的文件
  • 头文件位置
  • 源文件后缀
  • 系统头文件位置等
  • 编译时标志位
  • 预编译头文件及其标志位等


3.3 寻找系统头文件的位置

gcc -x c++ -v -E /dev/null
# or 
clang -x c++ -v -E /dev/null

实际运行结果:

#include <...> search starts here:
 /usr/include/c++/9
 /usr/include/x86_64-linux-gnu/c++/9
 /usr/include/c++/9/backward
 /usr/lib/gcc/x86_64-linux-gnu/9/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.


开始生成代码分析,对源码的类型进行分析:Classes,命令空间,结构体等

4. CodeReview

CodeReview是一种有意识和系统地召集其他程序员来检查代码是否有错误的地方的过程。它的主要目的是保证代码的质量。在敏捷团队中推行CodeReview,可以帮助团队快速成长。

4.1 CodeReview 的标准

  1. 代码风格:代码应符合组织或项目的代码风格指南,包括缩进、命名规范、注释等。

  2. 代码结构和逻辑:代码应具有清晰的结构和良好的逻辑,避免冗余、复杂或混乱的代码。

  3. 可读性:代码应易于理解和阅读,包括良好的命名、注释和文档。

  4. 性能:代码应具有良好的性能,避免低效的算法和操作。

  5. 安全性:代码应具有良好的安全性,避免潜在的安全漏洞和风险。

  6. 错误处理:代码应具有适当的错误处理机制,能够处理异常和错误情况。

  7. 单元测试:代码应具有良好的单元测试覆盖率,以确保代码的正确性和稳定性。

  8. 可维护性:代码应易于维护和扩展,包括模块化、可重用性和解耦等。

4.2 CodeReview的工具

  1. 静态代码分析工具:静态代码分析工具可以帮助检查代码中的潜在问题,例如代码中的错误、不规范的编码风格、潜在的性能问题等。常见的静态代码分析工具有SonarQube、PMD、FindBugs等。

  2. 版本控制工具:版本控制工具可以帮助团队协作开发和进行代码审查。常见的版本控制工具有Git、SVN等。

  3. 在线代码审查工具:在线代码审查工具可以帮助团队成员进行远程代码审查,方便团队成员之间的交流和反馈。常见的在线代码审查工具有GitHub、GitLab等。

  4. 代码评审工具:代码评审工具可以帮助团队成员进行代码质量评估和审核,包括代码风格、逻辑错误等方面。常见的代码评审工具有Crucible、Review Board等。

  5. 代码质量度量工具:代码质量度量工具可以帮助团队成员评估代码的质量,包括代码复杂度、测试覆盖率等方面。常见的代码质量度量工具有JaCoCo、Cobertura等。

  6. 测试工具:测试工具可以帮助团队成员进行单元测试、集成测试、性能测试等。常见的测试工具有JUnit、Selenium等。

参考连接

https://mp.weixin.qq.com/s/QDpYlvqSL4obaEZ_84JoPg

https://mp.weixin.qq.com/s/FxwON4tteWE5SZljW3zW6Q

https://zhuanlan.zhihu.com/p/96685579

https://mp.weixin.qq.com/s/m7YrrohPJaDkl1dXvK0jWg