C++ log:快速记录程序日志并提高调试效率

一、简介

在软件开发过程中,开发人员经常需要在程序不同阶段或者不同模块中输出一些调试信息,以方便查找问题以及调试程序。而程序的打印日志是一种非常便捷、常用的方法。

C++是一门强大的编程语言,拥有良好的性能和灵活的特性,在日志记录功能方面可以借助多种不同的库实现,而利用C++11标准库提供的特性,开发C++日志记录库是一种简单、轻量、灵活的方式。

二、如何实现日志记录

可以采用printf()函数输出日志信息,但这种方法有以下缺点:

  • 无法动态控制日志级别
  • 无法更改日志格式
  • 无法在不同平台上实现

因此我们需要使用C++日志库来完成这些功能,例如log4cpp、log4cplus、glog等。但这些库存在以下缺点:

  • 依赖性强,容易出现编译链接错误
  • 使用方法复杂,需要较长的学习周期
  • 库文件较大,占用磁盘空间

因此,我们可以自己开发一个轻量、简单的C++日志记录库。

三、实现

下面是利用C++11标准库实现的一个简单的日志记录库:

#include <iostream>
#include <fstream>
#include <chrono>
using namespace std;
//日志类型
enum log_type {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
};
//获取日志级别字符串
string get_log_level(log_type level) {
  static const string levels[] = {"DEBUG", "INFO", "WARN", "ERROR"};
  return levels[level];
}
//获取当前时间并返回字符串格式
string get_time() {
  time_t now_time = chrono::system_clock::to_time_t(chrono::system_clock::now());
  string time_str = ctime(&now_time);
  //去掉末尾的\n
  time_str.pop_back();
  return time_str;
}
//日志记录函数
void log(log_type level, const string& message) {
  static const string file_name = "log.txt";
  //打开文件
  ofstream ofs(file_name, ios_base::app);
  if (!ofs) {
    cerr << "Failed to open file: " << file_name << endl;
    return;
  }
  //获取时间和日志级别
  string time_str = get_time();
  string level_str = get_log_level(level);
  //写入日志
  ofs << "[" << time_str << "][" << level_str << "] " << message << endl;
}

以上的代码中,我们定义了日志类型log_type和相应的字符串表示。然后定义了一个函数get_time(),用于获取当前时间并返回字符串格式的时间。

接下来是最重要的函数log(log_type level, const string& message),该函数用于记录日志信息。在其中,我们首先指定了日志存储的文件名log.txt,并在函数的开头打开文件并检查是否打开成功。如果出错,函数将不执行任何操作。然后,在函数的主体中,我们获取当前时间和日志级别,并将这些信息连同日志信息一起写入到文件中。

最后是一个简单的示例,该示例记录了三个不同级别的日志:

int main() {
  log(DEBUG, "This is a debug message.");
  log(INFO, "This is an info message.");
  log(WARN, "This is a warning message.");
  log(ERROR, "This is an error message.");
  return 0;
}

该示例记录了四条日志,每条日志都包括了日志级别、时间戳和日志信息,将会被写入log.txt中:

[Sat Nov 21 14:10:57 2020][DEBUG] This is a debug message.
[Sat Nov 21 14:10:57 2020][INFO] This is an info message.
[Sat Nov 21 14:10:57 2020][WARN] This is a warning message.
[Sat Nov 21 14:10:57 2020][ERROR] This is an error message.

四、扩展

上面的C++日志库只具有基本的功能,如果想要更好地适应不同需求,我们可能需要对其进行一些扩展。

以下是对C++日志库扩展的一些常见需求:

1. 动态设置日志级别

在实际开发中,我们可能希望可以在运行时动态更改日志级别,例如实现一个命令行参数–log-level,使得程序可以接受不同的日志级别。

class Logger {
public:
  static Logger& instance() {
    static Logger logger;
    return logger;
  }

  void set_level(log_type level) {
    level_ = level;
  }

  void log(log_type level, const string& message) {
    if (level >= level_) {
      //do log
    }
  }

private:
  log_type level_ = INFO;
};

以上代码中,我们实现了一个Logger类,并将其设计为单例模式,使用instance()方法获取唯一实例。然后,我们添加了一个set_level()方法,用于动态设置日志级别,并在log()方法中增加了一个级别判断。在代码中,我们定义了一个全局日志级别变量level_,其默认值为INFO。

2. 日志异步写入

如果我们想要在日志记录和程序运行之间取得更好的平衡,我们可能会选择让程序异步写入日志文件。这样可以提高程序的响应性并提高程序的整体效率。

class AsyncLogger {
public:
  AsyncLogger() {
    //start logger thread
  }

  void log(log_type level, const string& message) {
    lock_guard lock(mutex_);
    queue_.emplace(level, message);
  }

  ~AsyncLogger() {
    //stop logger thread
  }

private:
  void logger_thread() {
    while (true) {
      unique_lock lock(mutex_);
      condition_.wait(lock, [this]() {
        return !queue_.empty() || stop_;
      });
      if (stop_ && queue_.empty()) {
        break;
      }
      auto log = queue_.front();
      queue_.pop();
      lock.unlock();
      //write log to file
    }
  }

  mutex mutex_;
  condition_variable condition_;
  queue<pair<log_type, string>> queue_;
  bool stop_ = false;
  thread logger_thread_;
};

以上代码中,我们实现了一个AsyncLogger类,该类具有一个内部队列和一个工作线程。用户通过调用log()方法将日志消息写入队列,而工作线程负责将这些消息异步写入到日志文件中。在类的析构函数中,停止工作线程。具有停止标志stop_,当设置为True时,工作线程会退出。

3. 预处理的日志消息

预处理的日志消息允许我们将日志消息转换为其它字符序列或格式。这样,我们就可以更灵活地控制日志消息的格式和记录方式。

#define LOG(level, message) \
  do {\
    stringstream ss;\
    ss << message;\
    log(level, ss.str()); \
  } while(0)

以上代码中,我们使用了一个C++的预处理器宏LOG(),该宏接受两个参数:一个日志级别和一个待记录的信息。当调用该宏时,宏展开为一个logg()函数调用,该函数将日志级别和记录的信息进行了连接。

五、总结

在这篇文章中,我们介绍了如何使用C++11标准库实现一个简单的日志记录库,该库可以直接使用,也可以基于需求进行扩展。实现一个简单的日志库并不难,我们只需要一些C++语言基础和文件流操作。但一旦你要进行扩展,例如添加异步写入、日志轮替等功能,那么你就需要更深入地了解C++11标准库的使用以及多线程编程,这是日志实现中难点之一。

原创文章,作者:EZJW,如若转载,请注明出处:https://www.506064.com/n/145149.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
EZJWEZJW
上一篇 2024-10-26 11:55
下一篇 2024-10-26 11:55

相关推荐

  • Java JsonPath 效率优化指南

    本篇文章将深入探讨Java JsonPath的效率问题,并提供一些优化方案。 一、JsonPath 简介 JsonPath是一个可用于从JSON数据中获取信息的库。它提供了一种DS…

    编程 2025-04-29
  • python强行终止程序快捷键

    本文将从多个方面对python强行终止程序快捷键进行详细阐述,并提供相应代码示例。 一、Ctrl+C快捷键 Ctrl+C快捷键是在终端中经常用来强行终止运行的程序。当你在终端中运行…

    编程 2025-04-29
  • Python程序需要编译才能执行

    Python 被广泛应用于数据分析、人工智能、科学计算等领域,它的灵活性和简单易学的性质使得越来越多的人喜欢使用 Python 进行编程。然而,在 Python 中程序执行的方式不…

    编程 2025-04-29
  • Ojlat:一款快速开发Web应用程序的框架

    Ojlat是一款用于快速开发Web应用程序的框架。它的主要特点是高效、易用、可扩展且功能齐全。通过Ojlat,开发人员可以轻松地构建出高质量的Web应用程序。本文将从多个方面对Oj…

    编程 2025-04-29
  • Python程序文件的拓展

    Python是一门功能丰富、易于学习、可读性高的编程语言。Python程序文件通常以.py为文件拓展名,被广泛应用于各种领域,包括Web开发、机器学习、科学计算等。为了更好地发挥P…

    编程 2025-04-29
  • Python购物车程序

    Python购物车程序是一款基于Python编程语言开发的程序,可以实现购物车的相关功能,包括商品的添加、购买、删除、统计等。 一、添加商品 添加商品是购物车程序的基础功能之一,用…

    编程 2025-04-29
  • Cron执行日志用法介绍

    本文将从多个方面对cron执行日志进行详细阐述,包括cron执行日志的定义、cron执行日志的产生原因、cron执行日志的格式以及如何解读cron执行日志。 一、定义 Cron是一…

    编程 2025-04-29
  • 爬虫是一种程序

    爬虫是一种程序,用于自动获取互联网上的信息。本文将从如下多个方面对爬虫的意义、运行方式、应用场景和技术要点等进行详细的阐述。 一、爬虫的意义 1、获取信息:爬虫可以自动获取互联网上…

    编程 2025-04-29
  • Vb运行程序的三种方法

    VB是一种非常实用的编程工具,它可以被用于开发各种不同的应用程序,从简单的计算器到更复杂的商业软件。在VB中,有许多不同的方法可以运行程序,包括编译器、发布程序以及命令行。在本文中…

    编程 2025-04-29
  • Hibernate日志打印sql参数

    本文将从多个方面介绍如何在Hibernate中打印SQL参数。Hibernate作为一种ORM框架,可以通过打印SQL参数方便开发者调试和优化Hibernate应用。 一、通过配置…

    编程 2025-04-29

发表回复

登录后才能评论