日志生成封装导出备份及存入数据库(xx面板厂监控项目)

功能拆分需求分析

流程图

开发环境工具:vs2022,HeidiSQL,mysql-8.0.31-winx64

层级:

.h文件

#pragma once
#include <string>
#include <fstream>
#include <vector>
#include <mysql.h>

// 定义宏,方便写入日志,使用可变长参数列表
#define LOGGER_INFO(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "INFO", fmt, ##__VA_ARGS__)
#define LOGGER_ERROR(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "ERROR", fmt, ##__VA_ARGS__)
#define LOGGER_WARNING(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "WARNING", fmt, ##__VA_ARGS__)
#define LOGGER_FATAL(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "FATAL", fmt, ##__VA_ARGS__)
#define LOGGER_DEBUG(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "DEBUG", fmt, ##__VA_ARGS__)

class Logger {
public:
    // 写入日志的函数
    static void
        WriteLogger(const char* file_name, const char* function_name, int line_number, const char* Log_level, const char* fmt, ...);

    // 设置查询者的ID
    static void SetUserID(int userID);

    static void InsertLogIntoDatabase(const std::string& user_name, const std::string& date,
        const std::string& function_name, int line_number, const std::string& Log_level,
        const std::string& description ,const std::string& file_name);

    static int GetUserID()
    {
        return userID;
    }

    Logger(const std::string& databaseConnectionString);
    ~Logger();

    // 写入日志到文件
    void WriteLogger(const std::string& log_info, int userID);
private:
    // 获取当前时间的函数
    static std::string GetCurrentTime_(const std::string& fmt);

    // 获取日志输出文件夹下的所有日志文件的函数
    static std::vector<std::string> GetLogFiles();

    // 生成新的日志文件名的函数
    static std::string GenLogFileName();

    // 字节换算单位
    static const int BYTE_UNIT = 1024;

    // 最大的日志文件个数
    static const int LOG_FILE_MAX_NUM = 4;

    // 日志文件最大大小
    static const int LOG_FILE_MAX_SIZE = 10 * BYTE_UNIT * BYTE_UNIT;

    // 日志可变长参数的长度
    static const int LOG_MSG_MAX_LEN = 512;

    // 一条完整的日志最大长度
    static const int LOG_FULL_MSG_MAX_LEN = 1024;

    // 日志中时间字符串的长度
    static const int LOG_TIME_BUFFER_LEN = 32;

    // 日志写入文件流
    static std::ofstream s_ofs;

    // 项目名称
    static std::string s_projectName;

    // 日志输出的文件夹路径
    static std::string s_logDirPath;

    // 日志输出的文件夹名称
    static std::string s_logDirName;

    // 检查并处理日志数量限制的函数定义
    static void CheckAndHandleLogLimitation(int maxLogs);

    // 记录查询者的ID
    static int userID;

    static const int maxLogCount = 10;
    static int ExecuteCountQuery(MYSQL* mysql, const std::string& query);
    static void ExecuteDeleteQuery(MYSQL* mysql, const std::string& query);
    static void ExecuteInsertQuery(MYSQL* mysql, const std::string& query);
    MYSQL* mysql; // MySQL连接对象
};

cpp源文件

#include "yejing_log.h"
#include <vector>
#include <cstdarg>
#include <ctime>
#include <algorithm>
#include <sys/stat.h>
#include <io.h>
#include <mysql.h>
#include <iostream>
#include <sys/stat.h>
#include <direct.h>

using namespace std;

ofstream Logger::s_ofs; // 静态成员变量的初始化

string Logger::s_projectName("yejingmianbanchang"); // 项目名称

string Logger::s_logDirPath; // 日志输出的文件夹路径

string Logger::s_logDirName("output"); // 日志输出的文件夹名称

int Logger::userID = 0;

// 写入日志的函数的实现
void Logger::WriteLogger(const char* file_name, const char* function_name, int line_number, const char* Log_level, const char* fmt, ...)
{
    // 获取当前的日志文件夹路径
    string currentFilePath = __FILE__; // __FILE__是预定义宏,表示当前文件的路径
    size_t pos = currentFilePath.find_last_of("/\\"); // 找到最后一个路径分隔符
    s_logDirPath = currentFilePath.substr(0, pos) + "/" + s_logDirName; // 构造日志文件夹路径

    if (!s_ofs.is_open()) { // 如果日志文件还没有打开
        struct stat dirInfo {}; // 用于存储文件夹信息的结构体
        string logFileName; // 日志文件名
        // 判断日志文件夹是否存在,如果不存在,则创建一个文件夹
        if (stat(s_logDirPath.c_str(), &dirInfo) != 0) { // stat函数用于获取文件信息,返回0表示成功
            _mkdir(s_logDirPath.c_str()); // 如果文件夹不存在,则创建一个新的文件夹
            logFileName = const_cast<char*>(GenLogFileName().c_str()); // 生成新的日志文件名
        }
        else { // 如果文件夹已经存在
            const vector<string>& files = GetLogFiles(); // 获取所有的日志文件
            if (files.empty()) { // 如果没有日志文件,则创建一个新的
                logFileName = const_cast<char*>(GenLogFileName().c_str());
            }
            else { // 如果有日志文件,则找到最近的那个日志文件
                logFileName = files.back();
            }
        }
        s_ofs.open(s_logDirPath + "/" + logFileName, ios::app); // 打开日志文件,以追加的方式写入
    }

    // 解析可变长参数
    va_list vaList;
    va_start(vaList, fmt); // 初始化可变长参数列表
    char logMsg[LOG_MSG_MAX_LEN] = { 0 }; // 存储日志信息的缓冲区
    vsprintf_s(logMsg, fmt, vaList); // 格式化日志信息
    va_end(vaList); // 结束可变长参数列表

    // (1)构造一条完整的日志
    // 2023-10-19 10:06:32 [login.c] [LoginCheck:50] [INFO] user admin login success
    char* logFileName = const_cast<char*>(GenLogFileName().c_str());
    char* p = const_cast<char*>(strrchr(file_name, '\\'));// 找到文件名中最后一个路径分隔符
    char filePath[256];
    strcpy_s(filePath, sizeof(filePath), file_name);
    //char* p = strrchr(filePath, '\\');

    if (p == nullptr) { // 如果没有找到,则找最后一个反斜杠
        p = strrchr(filePath, '\\');
    }
    // (2)获取当前的时间
    string currentTime = GetCurrentTime_("%Y-%m-%d %H:%M:%S"); // 获取当前时间
    char fullMsg[LOG_FULL_MSG_MAX_LEN] = { 0 }; // 存储完整日志信息的缓冲区
    sprintf_s(fullMsg, "%s [%s] [%s:%d] [%s] %s", currentTime.c_str(), p + 1, function_name, line_number, Log_level, logMsg); // 格式化完整日志信息
    // 判断文件大小,如果超过最大大小,则把上个文件关闭,重新打开文件
    if (s_ofs.tellp() >= LOG_FILE_MAX_SIZE) { // tellp函数用于获取当前写入指针的位置
        s_ofs.close(); // 关闭当前日志文件
        // 打开新的文件之前,要判断文件的个数,如果最大个数,则删除最远的那个日志文件
        const vector<string>& files = GetLogFiles(); // 获取所有的日志文件
        if (files.size() >= LOG_FILE_MAX_NUM) { // 如果已经达到最大数量,则删除最远的那个日志文件
            string delFilePath = s_logDirPath + "/" + files.front();
            remove(delFilePath.c_str());
        }
        string newFileName = GenLogFileName(); // 生成新的日志文件名
        s_ofs.open(s_logDirPath + "/" + newFileName, ios::app); // 打开新的日志文件
    }
    // (3)写入文件中
    s_ofs << fullMsg << endl; // 将完整日志信息写入日志文件
    s_ofs.flush(); // 刷新缓冲区,立即写入文件
    // 将日志信息插入到数据库中
    InsertLogIntoDatabase(std::to_string(userID), currentTime, function_name, line_number, Log_level, logMsg,file_name);

    // 在插入日志记录后检查并处理日志数量限制
    CheckAndHandleLogLimitation(LOG_FILE_MAX_NUM);
}

// 获取当前时间的函数的实现
std::string Logger::GetCurrentTime_(const std::string& fmt)
{
    time_t tt = time(nullptr); // 获取当前时间的时间戳
    struct tm tmPtr;
    localtime_s(&tmPtr, &tt);
    char buffer[LOG_TIME_BUFFER_LEN] = { 0 }; // 存储时间字符串的缓冲区
    strftime(buffer, sizeof(buffer), fmt.c_str(), &tmPtr); // 格式化时间字符串
    return buffer; // 返回时间字符串
}

// 获取日志输出文件夹下的所有日志文件的函数的实现
std::vector<string> Logger::GetLogFiles()
{
    vector<string> files; // 存储日志文件名的容器
    // 如何查找某个文件夹下的日志文件
    intptr_t fileHandle; // 文件句柄
    struct _finddata_t fileInfo {}; // 存储文件信息的结构体
    string dirPath = s_logDirPath + "/*.log"; // 构造日志文件夹路径
    if ((fileHandle = _findfirst(dirPath.c_str(), &fileInfo)) != -1) { // 查找第一个日志文件
        do {
            files.emplace_back(fileInfo.name); // 将日志文件名存入容器中
        } while (_findnext(fileHandle, &fileInfo) == 0); // 查找下一个日志文件
        _findclose(fileHandle); // 关闭文件句
    }
    // 将文件名按时间戳降序排列,保证最新的文件排在最前面
    sort(files.begin(), files.end(), [](const string& a, const string& b) {
        return stoll(a.substr(0, a.find('_'))) > stoll(b.substr(0, b.find('_')));
        });
    return files; // 返回日志文件名容器
}

// 生成新的日志文件名的函数的实现
std::string Logger::GenLogFileName()
{
    string prefix = to_string(time(nullptr)); // 以当前时间作为文件名前缀
    return prefix + "_" + s_projectName + ".log"; // 返回日志文件名
}

// 将日志信息插入到数据库的函数的实现
void Logger::InsertLogIntoDatabase(const std::string& user_name, const std::string& date, const std::string& function_name, int line_number, const std::string& Log_level, const std::string& description, const std::string&file_name )
{
    // 连接数据库
    MYSQL mysql;
    mysql_init(&mysql);
    // 建立数据库连接
    if (!mysql_real_connect(&mysql, "127.0.0.1", "root", "2632", "industry_project", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&mysql));
        return;
    }

    // 读取查询者的ID
    int newUserID = userID;
    // 在日志中记录查询者的ID
    std::string logMsgWithUserID = "User ID: " + std::to_string(newUserID) + "\n" + description;

    // 在这里执行数据库操作,包括日志条数判断、删除等
    int logCount = ExecuteCountQuery(&mysql, "SELECT COUNT(*) FROM t_log");
    if (logCount >= maxLogCount) {
        ExecuteDeleteQuery(&mysql, "DELETE FROM t_log WHERE some_condition");
    }
    //log_level这里存入数据莫名出现存不进去崩溃,escapedLevel的目的是确保level字符串中的特殊字符正确地进行转义,
    // 以使其可以安全地插入到SQL查询语句中。通过调用 mysql_real_escape_string 函数将 level 字符串中的特殊字符进行转义,
    // 并将转义后的结果存储在 escapedLevel 字符串中。
    std::string escapedLevel;
    escapedLevel.resize(Log_level.length() * 2 + 1);
    mysql_real_escape_string(&mysql, &escapedLevel[0], Log_level.c_str(), Log_level.length());
    int user_id_int = std::stoi(user_name);
    char query[512];
    sprintf_s(query,
        "INSERT INTO t_log(user_name, Log_level, date, function_name,file_name, line_number, description) VALUES ('%s', '%s', '%s', '%s', '%s',%d, '%s')",
        user_name.c_str(),
        escapedLevel.c_str(),
        date.c_str(),
        function_name.c_str(), 
        file_name.c_str(),
        line_number,
        description.c_str());

    if (mysql_query(&mysql, query) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        // 添加输出错误信息的语句
        std::cout << "MySQL error: " << mysql_error(&mysql) << std::endl;
        mysql_close(&mysql);
        return;
    }
    // 设置字符集为UTF8
    if (mysql_set_character_set(&mysql, "utf8") != 0) {
        fprintf(stderr, "Failed to set character set: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }
    // 执行SQL语句
    if (mysql_query(&mysql, query) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }

    int maxLogs = 100; // 设置日志数量限制,根据实际需求进行调整
    // 执行插入日志记录的代码

    // 在插入日志记录后检查并处理日志数量限制
    CheckAndHandleLogLimitation(maxLogs);
    // 关闭数据库连接
    mysql_close(&mysql);
}
void Logger::CheckAndHandleLogLimitation(int maxLogs)
{
    MYSQL mysql;
    mysql_init(&mysql);

    // 连接数据库
    if (!mysql_real_connect(&mysql, "127.0.0.1", "root", "2632", "industry_project", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&mysql));
        return;
    }

    MYSQL_RES* result;
    MYSQL_ROW row;
    int numRows;

    std::string query = "SELECT COUNT(*) FROM t_log";
    if (mysql_query(&mysql, query.c_str()) != 0) {
        // 处理查询失败的情况
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }

    result = mysql_store_result(&mysql);
    row = mysql_fetch_row(result);

    if (row != nullptr) {
        numRows = std::stoi(row[0]);

        if (numRows > maxLogs) {
            int numLogsToDelete = numRows - maxLogs;
            std::string deleteQuery = "DELETE FROM t_log ORDER BY date LIMIT " + std::to_string(numLogsToDelete);
            if (mysql_query(&mysql, deleteQuery.c_str()) != 0) {
                fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
            }
        }
    }

    mysql_free_result(result);
    mysql_close(&mysql);
}

void Logger::SetUserID(int newUserID) {
    // 设置查询者的ID
    userID = newUserID;
}

void Logger::WriteLogger(const std::string& description, int newUserID)
{
    // 在日志中记录查询者的ID
    std::string logMsgWithUserID = "User ID: " + std::to_string(newUserID) + "\n" + description;

    // 获取数据库中已存在的日志条数
    std::string countQuery = "SELECT COUNT(*) FROM logs;";
    int logCount = ExecuteCountQuery(mysql, countQuery);

    // 检查日志条数是否超过限定
    if (logCount >= maxLogCount)
    {
        // 删除最早的一条日志记录
        std::string deleteQuery = "DELETE FROM logs ORDER BY datetime ASC LIMIT 1;";
        ExecuteDeleteQuery(mysql, deleteQuery);
    }

    // 插入新的日志信息
    std::string insertQuery = "INSERT INTO logs (message, datetime) VALUES ('" + logMsgWithUserID + "', NOW());";
    ExecuteInsertQuery(mysql, insertQuery);

    // 写入日志到文件
    std::cout << logMsgWithUserID << std::endl;
}

Logger::Logger(const std::string& databaseConnectionString)
{
    mysql = mysql_init(nullptr);
    if (!mysql_real_connect(mysql, "127.0.0.1", "root", "2632", "industry_project", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(mysql));
        return;
    }
}

Logger::~Logger()
{
    mysql_close(mysql);
}
// 执行查询并返回结果
int Logger::ExecuteCountQuery(MYSQL* mysql, const std::string& query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
        return -1;
    }

    MYSQL_RES* result = mysql_store_result(mysql);
    if (result == nullptr) {
        fprintf(stderr, "Failed to retrieve result: Error: %s\n", mysql_error(mysql));
        return -1;
    }

    int rowCount = static_cast<int>(mysql_num_rows(result));

    // 释放结果集
    mysql_free_result(result);

    return rowCount;
}

// 执行删除操作
void Logger::ExecuteDeleteQuery(MYSQL* mysql, const std::string& query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
    }
}

// 执行插入操作
void Logger::ExecuteInsertQuery(MYSQL* mysql, const std::string& query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
    }
}

测试文件

#include "yejing_log.h"
#include <iostream>
using namespace std;

int main()
{
    // 设置查询者的ID
    Logger::SetUserID(121);
    // 获取查询者的ID
    int userID = Logger::GetUserID();
    LOGGER_INFO("%d %s %s", 123, "hello", "world"); // 记录一条INFO级别的日志
    LOGGER_ERROR("%d %s", 456, "error message"); // 记录一条ERROR级别的日志
    // 结束查询操作,重置查询者的ID
    Logger::SetUserID(0);
    return 0;
}

数据库表结构:

CREATE TABLE `t_log` (
    `user_name` VARCHAR(32) NULL DEFAULT NULL,
    `log_level` VARCHAR(8) NULL DEFAULT NULL,
    `date` VARCHAR(32) NULL DEFAULT NULL,
    `function_name` VARCHAR(32) NULL DEFAULT NULL,
    `file_name` VARCHAR(64) NULL DEFAULT NULL,
    `line_number` INT NULL DEFAULT NULL,
    `description` VARCHAR(32) NULL DEFAULT NULL
)
COLLATE='utf8mb3_general_ci'
ENGINE=InnoDB
;
 

日志生成信息示例:

开发环境工具:clion,HeidiSQL,mysql-8.0.31-winx64

.h文件

#include <string>
#include <fstream>
#include <vector>
#include <mysql.h>

// 定义宏,方便写入日志,使用可变长参数列表
#define LOGGER_INFO(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "INFO", fmt, ##__VA_ARGS__)
#define LOGGER_ERROR(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "ERROR", fmt, ##__VA_ARGS__)
#define LOGGER_WARNING(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "WARNING", fmt, ##__VA_ARGS__)
#define LOGGER_FATAL(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "FATAL", fmt, ##__VA_ARGS__)
#define LOGGER_DEBUG(fmt, ...) Logger::WriteLogger(__FILE__, __FUNCTION__, __LINE__, "DEBUG", fmt, ##__VA_ARGS__)

class Logger {
public:
    // 写入日志的函数
    static void
    WriteLogger(const char *filename, const char *funcname, int line, const char *level, const char *fmt, ...);

    // 设置查询者的ID
    static void SetUserID(int userID);

    static void InsertLogIntoDatabase(int user_id, const std::string &time, const std::string &filename,
                                      const std::string &funcname, int line, const std::string &level,
                                      const std::string &logMsg);

    static int GetUserID()
    {
        return userID;
    }

    Logger(const std::string& databaseConnectionString);
    ~Logger();

    // 写入日志到文件
    void WriteLogger(const std::string& logMsg, int userID);
private:
    // 获取当前时间的函数
    static std::string GetCurrentTime_(const std::string &fmt);

    // 获取日志输出文件夹下的所有日志文件的函数
    static std::vector<std::string> GetLogFiles();

    // 生成新的日志文件名的函数
    static std::string GenLogFileName();

    // 字节换算单位
    static const int BYTE_UNIT = 1024;

    // 最大的日志文件个数
    static const int LOG_FILE_MAX_NUM = 3;

    // 日志文件最大大小
    static const int LOG_FILE_MAX_SIZE = 10 * BYTE_UNIT * BYTE_UNIT;

    // 日志可变长参数的长度
    static const int LOG_MSG_MAX_LEN = 512;

    // 一条完整的日志最大长度
    static const int LOG_FULL_MSG_MAX_LEN = 1024;

    // 日志中时间字符串的长度
    static const int LOG_TIME_BUFFER_LEN = 32;

    // 日志写入文件流
    static std::ofstream s_ofs;

    // 项目名称
    static std::string s_projectName;

    // 日志输出的文件夹路径
    static std::string s_logDirPath;

    // 日志输出的文件夹名称
    static std::string s_logDirName;

    // 检查并处理日志数量限制的函数定义
    static void CheckAndHandleLogLimitation(int maxLogs);

    // 记录查询者的ID
    static int userID;

    static const int maxLogCount=10;
    static int ExecuteCountQuery(MYSQL* mysql, const std::string &query);
    static void ExecuteDeleteQuery(MYSQL* mysql, const std::string &query);
    static void ExecuteInsertQuery(MYSQL* mysql, const std::string &query);
    MYSQL* mysql; // MySQL连接对象

};

.cpp文件

#include "yejin_logger.h"
#include <vector>
#include <cstdarg>
#include <ctime>
#include <algorithm>
#include <sys/stat.h>
#include <io.h>
#include <mysql.h>
#include <iostream>
#include <sys/stat.h>

using namespace std;

ofstream Logger::s_ofs; // 静态成员变量的初始化

string Logger::s_projectName("yejingmianbanchang"); // 项目名称

string Logger::s_logDirPath; // 日志输出的文件夹路径

string Logger::s_logDirName("output"); // 日志输出的文件夹名称

int Logger::userID = 0;

// 写入日志的函数的实现
void Logger::WriteLogger(const char *filename, const char *funcname, int line, const char *level, const char *fmt, ...)
{
    // 获取当前的日志文件夹路径
    string currentFilePath = __FILE__; // __FILE__是预定义宏,表示当前文件的路径
    size_t pos = currentFilePath.find_last_of("/\\"); // 找到最后一个路径分隔符
    s_logDirPath = currentFilePath.substr(0, pos) + "/" + s_logDirName; // 构造日志文件夹路径

    if (!s_ofs.is_open()) { // 如果日志文件还没有打开
        struct stat dirInfo{}; // 用于存储文件夹信息的结构体
        string logFileName; // 日志文件名
        // 判断日志文件夹是否存在,如果不存在,则创建一个文件夹
        if (stat(s_logDirPath.c_str(), &dirInfo) != 0) { // stat函数用于获取文件信息,返回0表示成功
            mkdir(s_logDirPath.c_str()); // 如果文件夹不存在,则创建一个新的文件夹
            logFileName = GenLogFileName(); // 生成新的日志文件名
        } else { // 如果文件夹已经存在
            const vector<string> &files = GetLogFiles(); // 获取所有的日志文件
            if (files.empty()) { // 如果没有日志文件,则创建一个新的
                logFileName = GenLogFileName();
            } else { // 如果有日志文件,则找到最近的那个日志文件
                logFileName = files.back();
            }
        }
        s_ofs.open(s_logDirPath + "/" + logFileName, ios::app); // 打开日志文件,以追加的方式写入
    }

    // 解析可变长参数
    va_list vaList;
    va_start(vaList, fmt); // 初始化可变长参数列表
    char logMsg[LOG_MSG_MAX_LEN] = {0}; // 存储日志信息的缓冲区
    vsprintf(logMsg, fmt, vaList); // 格式化日志信息
    va_end(vaList); // 结束可变长参数列表

    // (1)构造一条完整的日志
    // 2023-10-19 10:06:32 [login.c] [LoginCheck:50] [INFO] user admin login success
    char *p = strrchr(filename, '/'); // 找到文件名中最后一个路径分隔符
    if (p == nullptr) { // 如果没有找到,则找最后一个反斜杠
        p = strrchr(filename, '\\');
    }
    // (2)获取当前的时间
    string currentTime = GetCurrentTime_("%Y-%m-%d %H:%M:%S"); // 获取当前时间
    char fullMsg[LOG_FULL_MSG_MAX_LEN] = {0}; // 存储完整日志信息的缓冲区
    sprintf(fullMsg, "%s [%s] [%s:%d] [%s] %s", currentTime.c_str(), p + 1, funcname, line, level, logMsg); // 格式化完整日志信息
    // 判断文件大小,如果超过最大大小,则把上个文件关闭,重新打开文件
    if (s_ofs.tellp() >= LOG_FILE_MAX_SIZE) { // tellp函数用于获取当前写入指针的位置
        s_ofs.close(); // 关闭当前日志文件
        // 打开新的文件之前,要判断文件的个数,如果最大个数,则删除最远的那个日志文件
        const vector<string> &files = GetLogFiles(); // 获取所有的日志文件
        if (files.size() >= LOG_FILE_MAX_NUM) { // 如果已经达到最大数量,则删除最远的那个日志文件
            string delFilePath = s_logDirPath + "/" + files.front();
            remove(delFilePath.c_str());
        }
        string newFileName = GenLogFileName(); // 生成新的日志文件名
        s_ofs.open(s_logDirPath + "/" + newFileName, ios::app); // 打开新的日志文件
    }
    // (3)写入文件中
    s_ofs << fullMsg << endl; // 将完整日志信息写入日志文件
    s_ofs.flush(); // 刷新缓冲区,立即写入文件

    // 将日志信息插入到数据库中
    InsertLogIntoDatabase(userID, currentTime, p + 1, funcname, line, level, logMsg);

    // 在插入日志记录后检查并处理日志数量限制
    CheckAndHandleLogLimitation(LOG_FILE_MAX_NUM);
}

// 获取当前时间的函数的实现
std::string Logger::GetCurrentTime_(const std::string &fmt)
{
    time_t tt = time(nullptr); // 获取当前时间的时间戳
    struct tm *tmPtr = localtime(&tt); // 将时间戳转换为本地时间
    char buffer[LOG_TIME_BUFFER_LEN] = {0}; // 存储时间字符串的缓冲区
    strftime(buffer, sizeof(buffer), fmt.c_str(), tmPtr); // 格式化时间字符串
    return buffer; // 返回时间字符串
}

// 获取日志输出文件夹下的所有日志文件的函数的实现
std::vector<string> Logger::GetLogFiles()
{
    vector<string> files; // 存储日志文件名的容器
    // 如何查找某个文件夹下的日志文件
    intptr_t fileHandle; // 文件句柄
    struct _finddata_t fileInfo{}; // 存储文件信息的结构体
    string dirPath = s_logDirPath + "/*.log"; // 构造日志文件夹路径
    if ((fileHandle = _findfirst(dirPath.c_str(), &fileInfo)) != -1) { // 查找第一个日志文件
        do {
            files.emplace_back(fileInfo.name); // 将日志文件名存入容器中
        } while (_findnext(fileHandle, &fileInfo) == 0); // 查找下一个日志文件
        _findclose(fileHandle); // 关闭文件句
    }
    // 将文件名按时间戳降序排列,保证最新的文件排在最前面
    sort(files.begin(), files.end(), [](const string& a, const string& b) {
        return stoll(a.substr(0, a.find('_'))) > stoll(b.substr(0, b.find('_')));
    });
    return files; // 返回日志文件名容器
}

// 生成新的日志文件名的函数的实现
std::string Logger::GenLogFileName()
{
    string prefix = to_string(time(nullptr)); // 以当前时间作为文件名前缀
    return prefix + "_" + s_projectName + ".log"; // 返回日志文件名
}

// 将日志信息插入到数据库的函数的实现
void Logger::InsertLogIntoDatabase(int user_id, const std::string &time, const std::string &filename, const std::string &funcname, int line, const std::string &level, const std::string &logMsg)
{
    // 连接数据库
    MYSQL mysql;
    mysql_init(&mysql);
    // 建立数据库连接
    if (!mysql_real_connect(&mysql, "127.0.0.1", "root", "2632", "yejin_test", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&mysql));
        return;
    }

    // 读取查询者的ID
    int newUserID = userID;
    // 在日志中记录查询者的ID
    std::string logMsgWithUserID = "User ID: " + std::to_string(newUserID) + "\n" + logMsg;

    // 在这里执行数据库操作,包括日志条数判断、删除等
    int logCount = ExecuteCountQuery(&mysql, "SELECT COUNT(*) FROM log_test");
    if (logCount >= maxLogCount) {
        ExecuteDeleteQuery(&mysql, "DELETE FROM log_test WHERE some_condition");
    }
    //log_level这里存入数据莫名出现存不进去崩溃,escapedLevel的目的是确保level字符串中的特殊字符正确地进行转义,
    // 以使其可以安全地插入到SQL查询语句中。通过调用 mysql_real_escape_string 函数将 level 字符串中的特殊字符进行转义,
    // 并将转义后的结果存储在 escapedLevel 字符串中。
    std::string escapedLevel;
    escapedLevel.resize(level.length() * 2 + 1);
    mysql_real_escape_string(&mysql, &escapedLevel[0], level.c_str(), level.length());

    char query[256];
    sprintf(query,
            "INSERT INTO log_test(user_id, Log_level, time, function_name, line_number, log_info) VALUES (%d, '%s', '%s', '%s', %d, '%s')",
            user_id,
            escapedLevel.c_str(),
            time.c_str(),
            funcname.c_str(),
            line,
            logMsg.c_str());

    if (mysql_query(&mysql, query) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        // 添加输出错误信息的语句
        std::cout << "MySQL error: " << mysql_error(&mysql) << std::endl;
        mysql_close(&mysql);
        return;
    }
    // 设置字符集为UTF8
    if (mysql_set_character_set(&mysql, "utf8") != 0) {
        fprintf(stderr, "Failed to set character set: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }
    // 执行SQL语句
    if (mysql_query(&mysql, query) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }

    int maxLogs = 100; // 设置日志数量限制,根据实际需求进行调整
    // 执行插入日志记录的代码

    // 在插入日志记录后检查并处理日志数量限制
    CheckAndHandleLogLimitation(maxLogs);
    // 关闭数据库连接
    mysql_close(&mysql);
}
void Logger::CheckAndHandleLogLimitation(int maxLogs)
{
    MYSQL mysql;
    mysql_init(&mysql);

    // 连接数据库
    if (!mysql_real_connect(&mysql, "127.0.0.1", "root", "2632", "yejin_test", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(&mysql));
        return;
    }

    MYSQL_RES *result;
    MYSQL_ROW row;
    int numRows;

    std::string query = "SELECT COUNT(*) FROM log_test";
    if (mysql_query(&mysql, query.c_str()) != 0) {
        // 处理查询失败的情况
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
        mysql_close(&mysql);
        return;
    }

    result = mysql_store_result(&mysql);
    row = mysql_fetch_row(result);

    if (row != nullptr) {
        numRows = std::stoi(row[0]);

        if (numRows > maxLogs) {
            int numLogsToDelete = numRows - maxLogs;
            std::string deleteQuery = "DELETE FROM log_test ORDER BY time LIMIT " + std::to_string(numLogsToDelete);
            if (mysql_query(&mysql, deleteQuery.c_str()) != 0) {
                fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(&mysql));
            }
        }
    }

    mysql_free_result(result);
    mysql_close(&mysql);
}

void Logger::SetUserID(int newUserID) {
    // 设置查询者的ID
    userID = newUserID;
}

void Logger::WriteLogger(const std::string& logMsg, int newUserID)
{
    // 在日志中记录查询者的ID
    std::string logMsgWithUserID = "User ID: " + std::to_string(newUserID) + "\n" + logMsg;

    // 获取数据库中已存在的日志条数
    std::string countQuery = "SELECT COUNT(*) FROM logs;";
    int logCount = ExecuteCountQuery(mysql, countQuery);

    // 检查日志条数是否超过限定
    if (logCount >= maxLogCount)
    {
        // 删除最早的一条日志记录
        std::string deleteQuery = "DELETE FROM logs ORDER BY datetime ASC LIMIT 1;";
        ExecuteDeleteQuery(mysql,deleteQuery);
    }

    // 插入新的日志信息
    std::string insertQuery = "INSERT INTO logs (message, datetime) VALUES ('" + logMsgWithUserID + "', NOW());";
    ExecuteInsertQuery(mysql,insertQuery);

    // 写入日志到文件
    std::cout << logMsgWithUserID << std::endl;
}

Logger::Logger(const std::string& databaseConnectionString)
{
    mysql = mysql_init(nullptr);
    if (!mysql_real_connect(mysql, "127.0.0.1", "root", "2632", "yejin_test", 3306, nullptr, 0)) {
        fprintf(stderr, "Failed to connect to database: Error: %s\n", mysql_error(mysql));
        return;
    }
}

Logger::~Logger()
{
    mysql_close(mysql);
}
// 执行查询并返回结果
int Logger::ExecuteCountQuery(MYSQL* mysql, const std::string &query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
        return -1;
    }

    MYSQL_RES* result = mysql_store_result(mysql);
    if (result == nullptr) {
        fprintf(stderr, "Failed to retrieve result: Error: %s\n", mysql_error(mysql));
        return -1;
    }

    int rowCount = static_cast<int>(mysql_num_rows(result));

    // 释放结果集
    mysql_free_result(result);

    return rowCount;
}

// 执行删除操作
void Logger::ExecuteDeleteQuery(MYSQL* mysql, const std::string &query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
    }
}

// 执行插入操作
void Logger::ExecuteInsertQuery(MYSQL* mysql, const std::string &query)
{
    if (mysql_query(mysql, query.c_str()) != 0) {
        fprintf(stderr, "Failed to execute query: Error: %s\n", mysql_error(mysql));
    }
}

测试文件

#include "yejin_logger.h"
#include <iostream>
using namespace std;

int main()
{
    // 设置查询者的ID
    Logger::SetUserID(141);
    // 获取查询者的ID
    int userID = Logger::GetUserID();
    LOGGER_INFO("%d %s %s", 123, "hello", "world"); // 记录一条INFO级别的日志
    //LOGGER_ERROR("%d %s", 456, "error message"); // 记录一条ERROR级别的日志
    // 结束查询操作,重置查询者的ID
    Logger::SetUserID(0);
    return 0;
}

数据库表结构:

CREATE TABLE `log_test` (
    `user_id` VARCHAR(50) NULL DEFAULT NULL,
    `Log_level` VARCHAR(50) NULL DEFAULT NULL,
    `time` DATETIME NULL,
    `function_name` VARCHAR(50) NULL,
    `line_number` INT NULL DEFAULT NULL,
    `log_info` VARCHAR(50) NULL,
    `file_name` VARCHAR(200) NULL DEFAULT NULL
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
;

运行生成日志信息示例:

日志封装生成导入数据库总结:
负责日志模块的实现:logger
需要完成的操作,将日志信息写入文件,并同步到数据库中

首先:明确思路和步骤
功能:需要实现日志的生成,把生成的信息封装到一个文件中,并同步记录写入数据库备份,方便其它模块或使用者查询日志记录
模块设计内部限定条件:
1.需要限定存入数据库的条数,不能无限存入日志信息(会增大数据库压力),数据库中达到一定条数,按照时间戳自动删除最早存入的日志信息
2.需要保证写入操作时,多次写入为同一文件,同时记录到数据库中(单例)
3.要规避避免大文件,文件日志写入信息到达一定大小,,换名新创建文件夹写入,并且需要实现文件翻滚的操作.换名算法,按编号,按时间戳
4.日志的不同级别操作,函数宏
5.记录的数据需要有,日志的级别,时间,用户id(名字)/成员变量,函数名,行号,描述(手动填写)       /  全部用宏,用宏操作替换出来

代码实现:

先梳理出来功能逻辑流程图和思路框架,然后开始搭建出来基本代码需要有的函数的功能框架,头文件和源文件

完善代码:
1.定义宏,方便写入不同级别的日志:使用宏定义,为每个日志级别定义一个宏,通过宏可以使其调用相应的日志函数,传入不同的参数。
2.实现logger类的静态方法:在logger类中实现了静态方法,WriteLogger,SetUserId和InsertLogIntoDatabase,用于写入日志,设置查询者的ID以及以及日志信息插入到数据库中,类中包含了静态成员变量和静态方法。
3.实现日志写入的文件逻辑:在InsertLogIntoDatabase方法中,使用文件流将日志信息写入文件。该方法会判断当前日志的文件大小和数量,当到达设定的限制时,会关闭当前日志文件并打开(创建)一个新的日志文件。
4.实现日志插入数据库的逻辑:在InsertLogIntoDatabase方法中,使用Mysql API来连接数据库,并执行相关查询和插入操作,该方法会判断当前日志数量是否达到限制,如果达到限制,会自动删除最早的一条记录。
5.完善其它辅助方法和成员变量:在Logger类中,还实现了一些辅助方法,如获取当前时间,获取日志文件列表等,以提高日志操作的灵活性和实用性.
6.写一个测试函数,测试代码,并提示其他同事在需要的时候,如何使用日志,通过调用相应的宏,简化了日志记录的步骤,提高了代码的可维护和可读性。id这里因项目其它模块暂未开发完全,手动设置了一个使用者id来配合其它函数的实现和存入(后期根据实际项目模块,去数据库中获取到当前使用者的id即可/需要修改获取id的函数实现)
7.去掉代码中的魔鬼数字,根据项目要求和基本规则完善规整并进一步优化代码。
8.测试bug,合项目代码。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值