功能拆分需求分析
流程图
开发环境工具: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,合项目代码。