C++开发基础之自定义异步日志库实现及性能测试

news2024/9/20 10:49:58

在这里插入图片描述

1. 前言

在软件开发中,日志记录是一个必不可少的部分。通过日志,我们可以记录系统的运行状态、错误信息以及调试数据。然而,当系统的日志量很大时,日志写入操作可能会影响系统的性能,尤其是在 I/O 操作较为频繁的情况下。因此,构建一个异步日志系统成为提升性能的重要手段。

在这篇博客中,我们将实现一个 C++ 异步日志库,支持日志级别分类和自定义文件路径、文件名等功能。同时,我们还会进行性能测试,评估异步日志系统的写入效率。


2. 异步日志系统的基本功能设计

1. 日志级别与日志结构

首先,我们需要定义日志的几种级别,并将其与日志消息、时间戳等信息封装在一起:

// 日志级别
enum class LogLevel {
    INFO,
    WARNING,
    ERROR,
    EXCEPTION
};

// 日志项
struct LogEntry {
    std::string message;
    LogLevel level;
    std::string timestamp;
};

我们定义了四种常见的日志级别:INFO(信息)、WARNING(警告)、 ERROR(错误)和EXCEPTION(异常)。每条日志都包含一条消息、日志级别和时间戳。

2. 获取当前时间和日期

为了记录日志的时间,我们需要获取当前系统时间并将其转换为标准的可读格式:

#include <chrono>
#include <ctime>
#include <sstream>
#include <iomanip>

// 获取当前时间的时间戳
std::string GetCurrentTimestamp() {
    auto now = std::chrono::system_clock::now();
    std::time_t now_time = std::chrono::system_clock::to_time_t(now);
    std::tm tm;
    localtime_s(&tm, &now_time);  
    std::stringstream ss;
    ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
    return ss.str();
}

// 获取当前日期,用于日志文件命名
std::string GetCurrentDate() {
    auto now = std::chrono::system_clock::now();
    std::time_t now_time = std::chrono::system_clock::to_time_t(now);
    std::tm tm;
    localtime_s(&tm, &now_time);
    std::stringstream ss;
    ss << std::put_time(&tm, "%Y-%m-%d");
    return ss.str();
}

GetCurrentTimestamp() 函数用于获取精确到秒的时间戳,而 GetCurrentDate() 用于生成日志文件的日期,以便我们根据日期创建日志文件。

3. 异步写入日志实现

接下来,我们构建一个 MyLogger 类,负责处理日志的异步写入。为了避免阻塞主线程,我们将日志写入操作放在单独的线程中处理,并使用 std::queue 存储待写入的日志。

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>

class MyLogger {
public:
    MyLogger(const std::string& output_dir)
        : output_dir_(output_dir), stop_flag_(false) {
        StartLoggingThread();
    }

    ~MyLogger() {
        StopLoggingThread();
    }

    // 记录日志
    void Log(const std::string& message, LogLevel level) {
        std::lock_guard<std::mutex> lock(queue_mutex_);
        log_queue_.emplace(LogEntry{ message, level, GetCurrentTimestamp() });
        condition_.notify_one();  // 通知日志线程有新日志
    }

private:
    std::string output_dir_;
    std::queue<LogEntry> log_queue_;
    std::mutex queue_mutex_;
    std::condition_variable condition_;
    bool stop_flag_;
    std::thread logging_thread_;

    // 启动日志线程
    void StartLoggingThread() {
        logging_thread_ = std::thread([this]() {
            while (!stop_flag_ || !log_queue_.empty()) {
                std::unique_lock<std::mutex> lock(queue_mutex_);
                condition_.wait(lock, [this]() {
                    return !log_queue_.empty() || stop_flag_;
                    });

                while (!log_queue_.empty()) {
                    LogEntry entry = log_queue_.front();
                    log_queue_.pop();
                    lock.unlock();
                    WriteLogToFile(entry);
                    lock.lock();
                }
            }
            });
    }

    // 停止日志线程
    void StopLoggingThread() {
        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            stop_flag_ = true;
        }
        condition_.notify_one();
        if (logging_thread_.joinable()) {
            logging_thread_.join();
        }
    }

    // 写入日志到文件
    void WriteLogToFile(const LogEntry& entry) {
        std::string filename = output_dir_ + "/log_" + GetCurrentDate() + ".txt";
        std::ofstream log_file(filename, std::ios_base::app);
        if (log_file.is_open()) {
            log_file << "[" << entry.timestamp << "] "
                << LogLevelToString(entry.level) << ": "
                << entry.message << std::endl;
        }
    }
};

4. 调用示例

我们可以创建一个 MyLogger 实例,并随时向其中添加日志:

// MyLogApp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "MyLogger.h"
#include <string>
#include <thread>
#include <filesystem>

namespace fs = std::filesystem;

int main()
{
    // 指定日志文件的输出路径
    std::string log_output_dir = "./logs";
    if (!fs::exists(log_output_dir)) {
        fs::create_directory(log_output_dir);
    }

    // 创建Logger实例
    MyLogger logger(log_output_dir);

    // 记录不同级别的日志
    logger.Log("This is an INFO message", LogLevel::INFO);
    logger.Log("This is a WARNING message", LogLevel::WARNING);
    logger.Log("This is an ERROR message", LogLevel::ERROR);
    logger.Log("This is an EXCEPTION message", LogLevel::EXCEPTION);
    // 等待一会,保证日志异步写入
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

在这个示例中,日志会被写入当前日期命名的文件中,例如 log_2024-09-06.txt。异步线程将自动处理日志写入,主线程不会因 I/O 操作而被阻塞。

[2024-09-06 15:07:02] INFO: This is an INFO message
[2024-09-06 15:07:02] WARNING: This is a WARNING message
[2024-09-06 15:07:02] ERROR: This is an ERROR message
[2024-09-06 15:07:02] EXCEPTION: This is an EXCEPTION message

3、日志库的性能测试

为了评估日志系统的性能,我们设计了一个简单的 Benchmark 测试。测试内容包括单次日志写入和批量日志写入的耗时。

1. 测试代码

我们使用 std::chrono 进行时间测量,并定义 LoggerBenchmark 类来测试性能:

#pragma once
#include "MyLogger.h"

class MyLoggerBenchmark 
{
public:
    MyLoggerBenchmark(MyLogger& logger) : logger_(logger) {}
    // 单次日志写入测试
    void SingleLogTest();
    // 批量日志写入测试
    void BulkLogTest(int num_logs);
private:
    MyLogger& logger_;
};
#pragma once

#include "MyLoggerBenchmark.h"

// 单次日志写入测试
void MyLoggerBenchmark::SingleLogTest() {
    auto start = std::chrono::high_resolution_clock::now();
    logger_.Log("Single log test message", LogLevel::INFO);
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
    std::cout << "Single log write took " << duration << " microseconds." << std::endl;
}

// 批量日志写入测试
void MyLoggerBenchmark::BulkLogTest(int num_logs) {
    std::vector<std::string> messages;
    for (int i = 0; i < num_logs; ++i) {
        messages.push_back("Bulk log test message #" + std::to_string(i + 1));
    }

    auto start = std::chrono::high_resolution_clock::now();
    for (const auto& msg : messages) {
        logger_.Log(msg, LogLevel::INFO);
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Bulk log write of " << num_logs << " logs took " << duration << " milliseconds." << std::endl;
    std::cout << "Average log write time: " << duration * 1000.0 / num_logs << " microseconds." << std::endl;
}

2. Benchmark 调用示例

// MyLogApp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <filesystem>
#include "MyLoggerBenchmark.h"

using namespace std;
namespace fs = std::filesystem;

int main()
{
    // 指定日志文件的输出路径
    std::string log_output_dir = "./logs";
    if (!fs::exists(log_output_dir)) {
        fs::create_directory(log_output_dir);
    }

    // 创建Logger实例
    MyLogger logger(log_output_dir);
    MyLoggerBenchmark benchmark(logger);
    // 单条日志写入测试
    benchmark.SingleLogTest();
    // 批量日志写入测试
    benchmark.BulkLogTest(100000);  // 写入 100000 条日志
    // 等待日志线程完成写入
    std::this_thread::sleep_for(std::chrono::seconds(2));
}

3. Benchmark 结果

我们通过批量写入 100000 条日志来测量日志系统的性能。输出如下:
日志文件记录

[2024-09-06 15:35:37] INFO: Single log test message
[2024-09-06 15:35:37] INFO: Bulk log test message #1
[2024-09-06 15:35:37] INFO: Bulk log test message #2
[2024-09-06 15:35:37] INFO: Bulk log test message #3
[2024-09-06 15:35:37] INFO: Bulk log test message #4
[2024-09-06 15:35:37] INFO: Bulk log test message #5
[2024-09-06 15:35:37] INFO: Bulk log test message #6
[2024-09-06 15:35:37] INFO: Bulk log test message #7
[2024-09-06 15:35:37] INFO: Bulk log test message #8
[2024-09-06 15:35:37] INFO: Bulk log test message #9
[2024-09-06 15:35:37] INFO: Bulk log test message #10
...
[2024-09-06 15:35:37] INFO: Bulk log test message #100000

控制台输出

Single log write took 2895 microseconds.
Bulk log write of 100000 logs took 1817 milliseconds.
Average log write time: 18.17 microseconds.

从结果中可以看出,异步日志系统在批量写入时效率较高,每条日志的平均写入时间大约为 18.17 微秒。


4. 总结

在本文中,我们实现了一个简单的 C++ 异步日志库,支持自定义日志文件命名、日志级别分类以及异步日志写入操作。通过测试,我们验证了异步日志系统在大量日志写入时的性能优势。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2112011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VR虚拟展厅的应用场景有哪些?

虚拟展厅作为一种利用虚拟现实技术构建的三维展示空间&#xff0c;其应用场景广泛且多样。视创云展为企业虚拟展厅搭建提供技术支持。以下是一些主要的应用场景&#xff1a; 1. 博物馆和艺术展览 文物保护与展示&#xff1a; 在博物馆中&#xff0c;为了保护珍贵的文物和艺术…

初识命名空间

1.创建两个命名空间 ip netns add host1 ip netns add host2 2. 查看命名空间 ip netns ls 3 、 创建veth ip -netns host1 link add veth0 type veth peer name host1-peer 4、 查看命名空间接口 ip -netns host1 address 5、 把host1-peer移动到host2命名空间 ip -ne…

编译过程例题

答案&#xff1a;A 知识点&#xff1a; 词法分析&#xff1a;从左到右逐个扫描源程序中的字符&#xff0c;识别其中如关键字&#xff0c;标识符&#xff0c;常数&#xff0c;运算符以及分隔符 语法分析&#xff1a;根据语法规则将单词符号分解成各类语法单位&#xff0c;并分…

1.2CubeMAX创建FREERTOS入门示例

1.CUBEMAX快速配置 V2改为V1否则编译会报错 2.Freertos各配置选项卡解释 Events &#xff1a;事件相关的创建 Task and Queues &#xff1a; 任务与队列的创建 Timers and Semaphores &#xff1a; 定时器和信号量的创建 Mutexes &#xff1a; 互斥量的创建 FreeRTOS Heap…

android之bootchart的使用

文章目录 简述流程 简述 主要是记录开机运行时的一些进程记录情况 流程 1.开启bootchart 输入以下命令 adb shell touch /data/bootchart/enabled然后重新启动设备&#xff0c;即可记录开机过程中的一些文件,如下所示 如果不想要bootchart进行记录&#xff0c;直接删除掉/…

STM32单片机HAL库——ADC输入

一、单通道采集 二、单通道DMA采集 使能DMA 三、定时器采集DMA传输 选择定时器1的通道1作为触发源&#xff0c;在TIM1的上升沿进行采集 定时器1挂载在APB2上面&#xff0c;APB2上定时器的频率为168MHZ psc168-1 arr1000-1 TIM1的时钟频率为168/168/10001kHZ pulse设置为500…

云计算之大数据(下)

目录 一、Hologres 1.1 产品定义 1.2 产品架构 1.3 Hologres基本概念 1.4 最佳实践 - Hologres分区表 1.5 最佳实践 - 分区字段设置 1.6 最佳实践 - 设置字段类型 1.7 最佳实践 - 存储属性设置 1.8 最佳实践 - 分布键设置 1.9 最佳实践 - 聚簇键设置 1.10 最佳实践 -…

C# WPF燃气报警器记录读取串口工具

C# WPF燃气报警器记录读取串口工具 概要串口帧数据布局文件代码文件运行效果源码下载 概要 符合国标文件《GB15322.2-2019.pdf》串口通信协议定义&#xff1b;可读取燃气报警器家用版设备历史记录信息等信息&#xff1b; 串口帧数据 串口通信如何确定一帧数据接收完成是个…

golang学习笔记06——怎么实现本地文件及目录监控-fsnotify

推荐学习文档 基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学习笔记04——如何真正写好Golang代码&…

在嵌入式板子上搭建和自定义live555服务器---编译问题和方法整理

live555 官方网站 点我直达&#xff0c;live555是一个简单的专注于实现RTSP服务器的开源库。它自带解析H264 H265 mp3等源的API&#xff0c;有一个简单的推流文件参考RTSP服务器例程testH264VideoStreamer也有官方实现的LIVE555 Media Server。无论是命令行使用还是用API实现定…

【最新华为OD机试E卷-支持在线评测】分糖果(100分)-多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,…

pyautogui进行点击失效,pyautogui.click()失效

背景&#xff1a;在Pycharm里&#xff0c;使用pythonpyautogui调用 .exe程序文件时候&#xff0c;当程序界面出来之后&#xff0c;鼠标失去反应&#xff0c;用pyautogui进行点击。后面尝试使用图片相似也无法实行点击。 解决方法&#xff1a;运行Pycharm或者其他ide的时候选择…

鸿蒙(API 12 Beta6版)超帧功能开发【顶点标记】

超帧提供两种运动估计模式供开发者选择&#xff1a;分别为基础模式和增强模式。其中增强模式需要对绘制顶点的Draw Call命令进行额外的标记&#xff0c;在相机和物体快速运动的游戏场景超帧效果较基础模式更优&#xff0c;能够有效改善拖影问题。本章主要介绍增强模式的运动估计…

【VB6|第27期】如何在VB6中使用Shell函数实现同步执行

日期&#xff1a;2024年9月1日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xff…

算法打卡——田忌赛马问题

问题简介&#xff1a;就是一个贪心的思想&#xff0c;下面上题目 要求示例输出输入 大体上先比较快马&#xff0c;田的快马与王的快马 其次比较田的慢马与王的慢马&#xff0c; 两处边界比较完全之后可以直接贪心了 几份示例的代码 代码一 #include <bits/stdc.h> …

【数据结构-二维前缀和】力扣1504. 统计全 1 子矩形

给你一个 m x n 的二进制矩阵 mat &#xff0c;请你返回有多少个 子矩形 的元素全部都是 1 。 示例 1&#xff1a; 输入&#xff1a;mat [[1,0,1],[1,1,0],[1,1,0]] 输出&#xff1a;13 解释&#xff1a; 有 6 个 1x1 的矩形。 有 2 个 1x2 的矩形。 有 3 个 2x1 的矩形。 有…

ia复习笔记

HCIA 常用配置以及快捷键&#xff1a;! 查看时间&#xff1a;display clock&#xff1b;修改时间&#xff1a;clock datetime 11:11:11 2023-1-1 查看设备当前的配置&#xff1a;display current-configuration&#xff1b;查看已保存的配置&#xff1a;display saved-config…

水晶连连看 - 无限版软件操作说明书

水晶连连看 – 无限版游戏软件使用说明书 文章目录 水晶连连看 – 无限版游戏软件使用说明书1 引言1.1 编写目的1.2 项目名称1.3 项目背景1.4 项目开发环境 2 概述2.1 目标2.2 功能2.3 性能 3 运行环境3.1 硬件3.2 软件 4 使用说明4.1 游戏开始界面4.2 游戏设定4.2.1 游戏帮助4…

docker拉取redis5.0.5并建立redis集群

1.配置文件 mkdir -p redis-cluster/7001/ mkdir -p redis-cluster/7002/ mkdir -p redis-cluster/7003/ mkdir -p redis-cluster/7004/ mkdir -p redis-cluster/7005/ mkdir -p redis-cluster/7006/cd redis-clustervim 7001/redis.confbind 0.0.0.0port 7001cluster-enabled…

传统CV算法——图像特征算法概述

文章目录 传统CV——图像特征算法概述1. 概述1.1 图像特征概述1. 2 局部特征1.2.1 定义1.2.2 特点1.2.3 常见方法1.2.4 应用 1.3 全局特征1.3.1 定义1.3.2 特点1.3.3 常见方法1.3.4 应用 1.4 局部特征与全局特征的比较1.5 局部特征点1.5.1 斑点与角点 1. 定义2. 特征3. 应用4. …