Skip to content

题目

设计一个日志系统,支持多线程安全的日志记录,并能够根据日期自动创建日志文件。该日志系统应包括全局日志和按日期分割的日志文件。

简介

在现代软件开发中,日志记录是调试和监控应用程序的重要工具。本题要求你实现一个多线程安全的日志系统,该系统能够同时记录全局日志和按日期分割的日志。全局日志将记录所有日志信息,而按日期分割的日志则每天生成一个新的日志文件。此外,当全局日志文件达到一定大小时,需要进行日志文件的轮转。

要求

  1. 多线程安全:确保在多线程环境下日志记录不会发生冲突。
  2. 日志级别:支持多种日志级别(如 Info, Debug, Warning, Error)。
  3. 全局日志
    • 记录所有日志信息到一个全局日志文件。
    • 当全局日志文件达到一定大小时(例如 10MB),进行日志文件的轮转。
  4. 按日期分割的日志
    • 每天生成一个新的日志文件。
    • 日志文件名格式为 YYYY-MM-DD.log
  5. 日志记录辅助类:提供一个简单的日志记录接口,方便用户使用。

实现工具

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

实现说明

  1. GlobalLogger 类

    • 单例模式,确保只有一个实例。
    • 支持日志级别的设置。
    • 当日志文件达到指定大小时,进行日志文件的轮转。
    • 使用互斥锁保证多线程安全。
  2. DateLogger 类

    • 每天生成一个新的日志文件。
    • 使用条件变量和队列来处理日志消息,确保多线程安全。
    • 在析构函数中等待工作线程完成并写入剩余的日志。
  3. Logger 辅助类

    • 提供一个简单的日志记录接口,方便用户使用。
    • 将日志消息同时发送到全局日志和按日期分割的日志。

测试

  • 编译并运行上述代码,观察生成的日志文件。
  • 尝试在多线程环境中使用该日志系统,验证其多线程安全性。
  • 修改日志级别,验证不同级别的日志是否正确记录。

知识如风,常伴吾身