题目
设计一个日志系统,支持多线程安全的日志记录,并能够根据日期自动创建日志文件。该日志系统应包括全局日志和按日期分割的日志文件。
简介
在现代软件开发中,日志记录是调试和监控应用程序的重要工具。本题要求你实现一个多线程安全的日志系统,该系统能够同时记录全局日志和按日期分割的日志。全局日志将记录所有日志信息,而按日期分割的日志则每天生成一个新的日志文件。此外,当全局日志文件达到一定大小时,需要进行日志文件的轮转。
要求
- 多线程安全:确保在多线程环境下日志记录不会发生冲突。
- 日志级别:支持多种日志级别(如 Info, Debug, Warning, Error)。
- 全局日志:
- 记录所有日志信息到一个全局日志文件。
- 当全局日志文件达到一定大小时(例如 10MB),进行日志文件的轮转。
- 按日期分割的日志:
- 每天生成一个新的日志文件。
- 日志文件名格式为
YYYY-MM-DD.log。
- 日志记录辅助类:提供一个简单的日志记录接口,方便用户使用。
实现工具
- 编程语言:C++
- 标准库:
<iostream>,<vector>,<ctime>,<cstdlib>,<mutex>,<thread>,<condition_variable>,<string>,<fstream>,<system_error>,<queue>,<windows.h>,<sstream>,<iomanip>,<chrono>
案例
代码框架
cpp
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <string>
#include <fstream>
#include <system_error>
#include <queue>
#include <windows.h>
#include <sstream>
#include <iomanip>
#include <chrono>
// 日志级别定义
enum class LogLevel {
Info,
Debug,
Warning,
Error,
};
// 前向声明
class DateLogger;
// 全局日志类
class GlobalLogger {
private:
std::ofstream globalLogFile;
static std::mutex mutex_alone;
LogLevel logLevel = LogLevel::Info;
const size_t MAX_LOG_SIZE = 1024 * 1024 * 10; // 10MB
GlobalLogger() {
globalLogFile.open("app.log", std::ios::app);
if (!globalLogFile.is_open()) {
throw std::runtime_error("Failed to open global log file");
}
}
GlobalLogger(const GlobalLogger&) = delete;
GlobalLogger& operator=(const GlobalLogger&) = delete;
std::string getCurrentTime() {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
localtime_s(&tm, &in_time_t);
std::stringstream ss;
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
return ss.str();
}
void rotateFileIfNeeded() {
std::lock_guard<std::mutex> lock(mutex_alone);
auto pos = globalLogFile.tellp();
if (pos >= MAX_LOG_SIZE) {
globalLogFile.close();
std::string newName = "app_" + getCurrentTime() + ".log";
if (std::rename("app.log", newName.c_str())) {
throw std::system_error(errno, std::system_category(), "Failed to rotate log file");
}
globalLogFile.open("app.log", std::ios::app);
if (!globalLogFile.is_open()) {
throw std::runtime_error("Failed to reopen log file");
}
}
}
public:
static GlobalLogger& getInstance() {
static GlobalLogger instance;
return instance;
}
void setLogLevel(LogLevel level) {
logLevel = level;
}
void log(LogLevel level, const std::string& message) {
if (level < logLevel) return;
rotateFileIfNeeded();
std::lock_guard<std::mutex> lock(mutex_alone);
if (globalLogFile.is_open()) {
globalLogFile << "[" << getCurrentTime() << "] "
<< static_cast<int>(level) << " "
<< message << std::endl;
}
}
~GlobalLogger() {
if (globalLogFile.is_open()) {
globalLogFile.close();
}
}
};
std::mutex GlobalLogger::mutex_alone;
// 日期日志类
class DateLogger {
private:
std::ofstream dateLogFile;
static std::mutex m_mutex;
std::condition_variable m_condition;
std::queue<std::string> m_logQueue_;
bool running = true;
std::thread worker_;
void processLog() {
while (true) {
std::queue<std::string> localQueue;
{
std::unique_lock<std::mutex> lock(m_mutex);
m_condition.wait(lock, [this] {
return !running || !m_logQueue_.empty();
});
if (!running && m_logQueue_.empty()) break;
localQueue.swap(m_logQueue_);
}
writeToFile(localQueue);
}
}
void writeToFile(std::queue<std::string>& queue) {
std::string filename = getTodayFilename();
std::ofstream file(filename, std::ios::app);
if (!file.is_open()) {
std::cerr << "Failed to open date log file: " << filename << std::endl;
return;
}
while (!queue.empty()) {
file << queue.front() << std::endl;
queue.pop();
}
}
std::string getTodayFilename() {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
localtime_s(&tm, &in_time_t);
std::stringstream ss;
ss << std::put_time(&tm, "%Y-%m-%d") << ".log";
return ss.str();
}
public:
DateLogger() : worker_([this] { processLog(); }) {}
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(m_mutex);
m_logQueue_.push(message);
m_condition.notify_one();
}
~DateLogger() {
{
std::lock_guard<std::mutex> lock(m_mutex);
running = false;
}
m_condition.notify_one();
worker_.join();
// Flush remaining logs
std::queue<std::string> remaining;
{
std::lock_guard<std::mutex> lock(m_mutex);
remaining.swap(m_logQueue_);
}
writeToFile(remaining);
}
};
std::mutex DateLogger::m_mutex;
// 日志辅助类
class Logger {
private:
class LogMessage {
GlobalLogger& globalLogger;
DateLogger& dateLogger;
LogLevel level;
std::ostringstream stream;
public:
LogMessage(GlobalLogger& gl, DateLogger& dl, LogLevel lvl)
: globalLogger(gl), dateLogger(dl), level(lvl) {}
~LogMessage() {
const std::string message = stream.str();
globalLogger.log(level, message);
dateLogger.log(message);
}
template<typename T>
LogMessage& operator<<(const T& value) {
stream << value;
return *this;
}
};
public:
static LogMessage log(LogLevel level) {
static GlobalLogger& gl = GlobalLogger::getInstance();
static DateLogger dl;
return LogMessage(gl, dl, level);
}
};
// 使用示例
int main() {
try {
Logger::log(LogLevel::Info) << "Application started";
Logger::log(LogLevel::Debug) << "Debugging information";
Logger::log(LogLevel::Warning) << "Low disk space";
Logger::log(LogLevel::Error) << "Critical error occurred!";
} catch (const std::exception& e) {
std::cerr << "Logging error: " << e.what() << std::endl;
return 1;
}
return 0;
}实现说明
GlobalLogger 类:
- 单例模式,确保只有一个实例。
- 支持日志级别的设置。
- 当日志文件达到指定大小时,进行日志文件的轮转。
- 使用互斥锁保证多线程安全。
DateLogger 类:
- 每天生成一个新的日志文件。
- 使用条件变量和队列来处理日志消息,确保多线程安全。
- 在析构函数中等待工作线程完成并写入剩余的日志。
Logger 辅助类:
- 提供一个简单的日志记录接口,方便用户使用。
- 将日志消息同时发送到全局日志和按日期分割的日志。
测试
- 编译并运行上述代码,观察生成的日志文件。
- 尝试在多线程环境中使用该日志系统,验证其多线程安全性。
- 修改日志级别,验证不同级别的日志是否正确记录。
