题目
实现一个双日志系统,该系统包含两个日志记录器:GlobalLogger 和 DateLogger。GlobalLogger 记录所有日志消息到一个全局日志文件,而 DateLogger 将日志消息按日期分别记录到不同的日志文件中。日志消息通过 Logger 类进行统一管理,并支持不同日志级别(如 Info, Debug, Warning, Error)。
简介
在多线程环境中,日志记录是一个常见的需求。本题要求你实现一个双日志系统,其中包括 GlobalLogger 和 DateLogger 两个日志记录器。GlobalLogger 将所有日志消息记录到一个全局日志文件中,而 DateLogger 将日志消息按日期分别记录到不同的日志文件中。日志消息通过 Logger 类进行统一管理,并支持不同日志级别。
要求
- 多线程安全:确保在多线程环境下数据访问和更新不会发生冲突。
- 双日志记录器:
GlobalLogger:将所有日志消息记录到一个全局日志文件中。DateLogger:将日志消息按日期分别记录到不同的日志文件中。
- 日志级别:支持不同日志级别(如 Info, Debug, Warning, Error)。
- 日志消息格式化:日志消息应包含时间戳和日志级别。
- 异常处理:在文件打开失败或其他错误时,捕获并处理异常。
实现工具
- 编程语言:C++
- 标准库:
<iostream>,<fstream>,<sstream>,<chrono>,<mutex>,<condition_variable>,<thread>,<string>,<stdexcept>,<ctime>
代码实现
cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <string>
#include <stdexcept>
#include <ctime>
// 日志级别枚举
enum class LogLevel {
Info,
Debug,
Warning,
Error
};
// 全局日志记录器
class GlobalLogger {
public:
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(m_mutex);
m_logQueue.push(message);
m_condition.notify_one();
}
private:
std::queue<std::string> m_logQueue;
std::mutex m_mutex;
std::condition_variable m_condition;
std::thread worker;
GlobalLogger() : worker([this] { processLog(); }) {}
void processLog() {
while (true) {
std::queue<std::string> localQueue;
{
std::unique_lock<std::mutex> lock(m_mutex);
m_condition.wait(lock, [this] {
return !m_logQueue.empty();
});
if (!m_logQueue.empty()) {
localQueue.swap(m_logQueue);
}
}
writeToFile(localQueue, "global.log");
}
}
void writeToFile(std::queue<std::string>& queue, const std::string& filename) {
std::ofstream file(filename, std::ios::app);
if (!file.is_open()) {
std::cerr << "Failed to open global log file: " << filename << std::endl;
return;
}
while (!queue.empty()) {
file << queue.front() << std::endl;
queue.pop();
}
}
static GlobalLogger& getInstance() {
static GlobalLogger instance;
return instance;
}
};
// 按日期的日志记录器
class DateLogger {
public:
DateLogger() : worker_([this] { processLog(); }), running(true) {}
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_all();
worker_.join();
}
private:
std::queue<std::string> m_logQueue_;
std::mutex m_mutex;
std::condition_variable m_condition;
std::thread worker_;
bool running;
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();
}
};
// 日志消息类
class LogMessage {
public:
LogMessage(GlobalLogger& gl, DateLogger& dl, LogLevel level)
: globalLogger(gl), dateLogger(dl), level(level) {}
template<typename T>
LogMessage& operator<<(const T& value) {
stream << value;
return *this;
}
~LogMessage() {
std::ostringstream oss;
oss << "[" << getCurrentTime() << "] [" << getLogLevelString(level) << "] " << stream.str();
globalLogger.log(oss.str());
dateLogger.log(oss.str());
}
private:
GlobalLogger& globalLogger;
DateLogger& dateLogger;
LogLevel level;
std::ostringstream stream;
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();
}
std::string getLogLevelString(LogLevel level) {
switch (level) {
case LogLevel::Info: return "INFO";
case LogLevel::Debug: return "DEBUG";
case LogLevel::Warning: return "WARNING";
case LogLevel::Error: return "ERROR";
default: return "UNKNOWN";
}
}
};
// 日志管理类
class Logger {
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;
}代码说明
日志级别枚举:
LogLevel枚举定义了四种日志级别:Info, Debug, Warning, Error。
GlobalLogger 类:
GlobalLogger类负责将所有日志消息记录到一个全局日志文件中。log方法将日志消息添加到队列中,并通知工作线程处理。processLog方法在一个单独的线程中运行,负责从队列中取出日志消息并写入文件。writeToFile方法将日志消息写入指定的日志文件。getInstance方法提供了单例模式,确保只有一个GlobalLogger实例。
DateLogger 类:
DateLogger类负责将日志消息按日期分别记录到不同的日志文件中。log方法将日志消息添加到队列中,并通知工作线程处理。processLog方法在一个单独的线程中运行,负责从队列中取出日志消息并写入文件。writeToFile方法将日志消息写入当前日期的日志文件。getTodayFilename方法生成当前日期的日志文件名。- 析构函数确保在对象销毁时停止工作线程。
LogMessage 类:
LogMessage类用于格式化日志消息,并将其发送到GlobalLogger和DateLogger。operator<<方法允许链式调用,方便构建日志消息。- 析构函数在对象销毁时自动发送日志消息。
getCurrentTime方法获取当前时间戳。getLogLevelString方法将日志级别转换为字符串表示。
Logger 类:
Logger类提供了一个静态方法log,用于创建LogMessage对象。
主函数:
main函数演示了如何使用Logger类记录不同级别的日志消息。
测试
- 编译并运行上述代码,观察输出的日志文件。
- 尝试记录不同级别的日志消息,验证日志记录是否正确。
- 检查日志文件的内容,确保时间戳和日志级别正确。
好的,下面是一个对比结果图,展示了 双日志系统1.0 和 双日志系统2.0` 各自的优缺点。
对比结果图
| 特性/版本 | 双日志系统1.0.pdf | 双日志系统1.1.pdf |
|---|---|---|
| 错误消息语言 | 中文 | 英文 |
| 优点 | - 更符合中文环境 - 易于理解 | - 国际化 - 适合多语言环境 |
| 缺点 | - 不利于国际化 | - 对中文用户可能不够友好 |
| 方法命名 | 驼峰命名法(如 GetCurrentTime) | 小写下划线命名法(如 get_current_time) |
|---|---|---|
| 优点 | - 符合C++传统命名规范 | - 符合某些编码规范(如Google C++ Style Guide) - 一致性更好 |
| 缺点 | - 与某些现代编码规范不一致 | - 可能不符合某些团队的传统命名规范 |
| 日期格式化方式 | 使用 strftime | 使用 std::put_time 和 std::stringstream |
|---|---|---|
| 优点 | - 简单直接 | - 更灵活 - 更现代 |
| 缺点 | - 功能较为有限 | - 相对复杂 |
| 单例模式实现 | DateLogger 的 GetInstance | GlobalLogger 的 getInstance |
|---|---|---|
| 优点 | - 逻辑清晰 | - 符合小写命名规范 |
| 缺点 | - 类名和方法名不一致 | - 类名不同,可能导致混淆 |
| 重命名日志文件 | 包含 RenameLogFile 方法 | 没有明确提到 |
|---|---|---|
| 优点 | - 处理日志文件大小限制 | - 简洁,没有冗余代码 |
| 缺点 | - 增加了复杂性 | - 缺乏对日志文件大小的管理 |
| 日志消息格式化 | 直接构建并输出日志条目 | 使用 std::ostringstream 构建日志条目并通过 GlobalLogger 和 DateLogger 记录 |
|---|---|---|
| 优点 | - 简单直接 | - 灵活性高 - 易于扩展 |
| 缺点 | - 扩展性较差 | - 相对复杂 |
| 日志级别处理 | 根据日志级别调用不同的方法获取日志消息 | 没有明确提到 |
|---|---|---|
| 优点 | - 细粒度控制 | - 简洁,没有冗余代码 |
| 缺点 | - 代码复杂度增加 | - 缺乏对不同日志级别的处理 |
**双日志系统1.0:
- 优点: 适合中文环境,方法命名符合传统C++规范,包含重命名日志文件的功能,细粒度的日志级别处理。
- 缺点: 不利于国际化,类名和方法名不一致,代码复杂度较高。
双日志系统2.0:
- 优点: 国际化,符合现代编码规范,灵活性高,易于扩展,简洁。
- 缺点: 对中文用户可能不够友好,缺乏对日志文件大小的管理和细粒度的日志级别处理。
