Skip to content

题目

实现一个双日志系统,该系统包含两个日志记录器:GlobalLoggerDateLoggerGlobalLogger 记录所有日志消息到一个全局日志文件,而 DateLogger 将日志消息按日期分别记录到不同的日志文件中。日志消息通过 Logger 类进行统一管理,并支持不同日志级别(如 Info, Debug, Warning, Error)。

简介

在多线程环境中,日志记录是一个常见的需求。本题要求你实现一个双日志系统,其中包括 GlobalLoggerDateLogger 两个日志记录器。GlobalLogger 将所有日志消息记录到一个全局日志文件中,而 DateLogger 将日志消息按日期分别记录到不同的日志文件中。日志消息通过 Logger 类进行统一管理,并支持不同日志级别。

要求

  1. 多线程安全:确保在多线程环境下数据访问和更新不会发生冲突。
  2. 双日志记录器
    • GlobalLogger:将所有日志消息记录到一个全局日志文件中。
    • DateLogger:将日志消息按日期分别记录到不同的日志文件中。
  3. 日志级别:支持不同日志级别(如 Info, Debug, Warning, Error)。
  4. 日志消息格式化:日志消息应包含时间戳和日志级别。
  5. 异常处理:在文件打开失败或其他错误时,捕获并处理异常。

实现工具

  • 编程语言: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;
}

代码说明

  1. 日志级别枚举

    • LogLevel 枚举定义了四种日志级别:Info, Debug, Warning, Error。
  2. GlobalLogger 类

    • GlobalLogger 类负责将所有日志消息记录到一个全局日志文件中。
    • log 方法将日志消息添加到队列中,并通知工作线程处理。
    • processLog 方法在一个单独的线程中运行,负责从队列中取出日志消息并写入文件。
    • writeToFile 方法将日志消息写入指定的日志文件。
    • getInstance 方法提供了单例模式,确保只有一个 GlobalLogger 实例。
  3. DateLogger 类

    • DateLogger 类负责将日志消息按日期分别记录到不同的日志文件中。
    • log 方法将日志消息添加到队列中,并通知工作线程处理。
    • processLog 方法在一个单独的线程中运行,负责从队列中取出日志消息并写入文件。
    • writeToFile 方法将日志消息写入当前日期的日志文件。
    • getTodayFilename 方法生成当前日期的日志文件名。
    • 析构函数确保在对象销毁时停止工作线程。
  4. LogMessage 类

    • LogMessage 类用于格式化日志消息,并将其发送到 GlobalLoggerDateLogger
    • operator<< 方法允许链式调用,方便构建日志消息。
    • 析构函数在对象销毁时自动发送日志消息。
    • getCurrentTime 方法获取当前时间戳。
    • getLogLevelString 方法将日志级别转换为字符串表示。
  5. Logger 类

    • Logger 类提供了一个静态方法 log,用于创建 LogMessage 对象。
  6. 主函数

    • main 函数演示了如何使用 Logger 类记录不同级别的日志消息。

测试

  • 编译并运行上述代码,观察输出的日志文件。
  • 尝试记录不同级别的日志消息,验证日志记录是否正确。
  • 检查日志文件的内容,确保时间戳和日志级别正确。

好的,下面是一个对比结果图,展示了 双日志系统1.0 和 双日志系统2.0` 各自的优缺点。

对比结果图

特性/版本双日志系统1.0.pdf双日志系统1.1.pdf
错误消息语言中文英文
优点- 更符合中文环境
- 易于理解
- 国际化
- 适合多语言环境
缺点- 不利于国际化- 对中文用户可能不够友好
方法命名驼峰命名法(如 GetCurrentTime小写下划线命名法(如 get_current_time
优点- 符合C++传统命名规范- 符合某些编码规范(如Google C++ Style Guide)
- 一致性更好
缺点- 与某些现代编码规范不一致- 可能不符合某些团队的传统命名规范
日期格式化方式使用 strftime使用 std::put_timestd::stringstream
优点- 简单直接- 更灵活
- 更现代
缺点- 功能较为有限- 相对复杂
单例模式实现DateLoggerGetInstanceGlobalLoggergetInstance
优点- 逻辑清晰- 符合小写命名规范
缺点- 类名和方法名不一致- 类名不同,可能导致混淆
重命名日志文件包含 RenameLogFile 方法没有明确提到
优点- 处理日志文件大小限制- 简洁,没有冗余代码
缺点- 增加了复杂性- 缺乏对日志文件大小的管理
日志消息格式化直接构建并输出日志条目使用 std::ostringstream 构建日志条目并通过 GlobalLoggerDateLogger 记录
优点- 简单直接- 灵活性高
- 易于扩展
缺点- 扩展性较差- 相对复杂
日志级别处理根据日志级别调用不同的方法获取日志消息没有明确提到
优点- 细粒度控制- 简洁,没有冗余代码
缺点- 代码复杂度增加- 缺乏对不同日志级别的处理
  • **双日志系统1.0:

    • 优点: 适合中文环境,方法命名符合传统C++规范,包含重命名日志文件的功能,细粒度的日志级别处理。
    • 缺点: 不利于国际化,类名和方法名不一致,代码复杂度较高。
  • 双日志系统2.0:

    • 优点: 国际化,符合现代编码规范,灵活性高,易于扩展,简洁。
    • 缺点: 对中文用户可能不够友好,缺乏对日志文件大小的管理和细粒度的日志级别处理。

知识如风,常伴吾身