重构下代码
好歹是 c++,老是面向过程怎么行,比如我想在多个文件调用写日志,我还得把log_message
改成全局函数,如果我想写进不同文件,我还得改造实现逻辑将文件路径传入log_message
,这也太蠢了。那我们重构下:
第一次优化:创建 Logger 类
- 创建日志器类:为了提高代码的模块化和可重用性,我们将创建一个日志器类,将所有与日志记录相关的功能封装在其中。
- 移除静态日志文件和线程池对象:我们将把它们作为日志器类的成员变量,而不是静态变量。这样可以在需要时创建多个日志器实例。
#include <fstream>
#include <iostream>
#include <string>
#include <ctime>
#include <fmt/core.h>
#include <thread>
#include <chrono>
#include <mutex>
#include "include/ThreadPool.h"
class Logger {
public:
enum LogLevel {
INFO,
WARNING,
ERROR
};
Logger(const std::string& logFilePath, size_t poolSize = 1) : log_file(logFilePath, std::ios::app), log_pool(poolSize, 1000, async_overflow_policy::block) {
if (!log_file.is_open()) {
throw std::runtime_error("无法打开日志文件!");
}
}
~Logger() {
shutdown();
}
// Shutdown method to stop accepting new log messages and flush all pending logs
void shutdown() {
// Since the ThreadPool destructor will join all threads,
// we just need to ensure that no new tasks are enqueued after calling shutdown.
log_file.close(); // Close the log file after all tasks are done.
}
template <typename... Args>
void log(LogLevel level, const std::string& format, Args... args) {
log_pool.enqueue([this, level, format, args...] {
std::lock_guard<std::mutex> lock(log_mutex);
auto log_entry = fmt::format("[{}] [{}] {}\n", currentDateTime(), toString(level), fmt::format(format, args...));
log_file << log_entry;
});
}
private:
std::ofstream log_file;
ThreadPool log_pool;
mutable std::mutex log_mutex; // 保护文件写入操作
const char* toString(LogLevel level) const {
switch(level) {
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
std::string currentDateTime() const {
std::time_t now = std::time(nullptr);
std::string dt = std::ctime(&now);
dt.pop_back(); // 移除换行符
return dt;
}
};
int main() {
try {
Logger logger("log.txt", 10);
logger.log(Logger::INFO, "这是一条信息级别的消息。");
logger.log(Logger::ERROR, "错误代码:{}. 错误信息:{}", 404, "未找到");
} catch(const std::exception& e) {
std::cerr << "日志器初始化失败: " << e.what() << std::endl;
return -1;
}
return 0;
}
我们每次一点点改,改完后确保功能正常。
第二次优化:管理同步、异步 Logger 类
我们不可能只支持异步,同步顺序写也很重要,这里我们创建两个类,一个同步类Logger
,一个异步类AsyncLogger
继承于Logger
#include <fstream>
#include <iostream>
#include <string>
#include <ctime>
#include <fmt/core.h>
#include <thread>
#include <chrono>
#include <mutex>
#include "include/ThreadPool.h"
class Logger {
public:
enum LogLevel {
INFO,
WARNING,
ERROR
};
Logger(const std::string& logFilePath) : log_file(logFilePath, std::ios::app) {
if (!log_file.is_open()) {
throw std::runtime_error("无法打开日志文件!");
}
}
virtual ~Logger() {
log_file.close();
}
template <typename... Args>
void log(LogLevel level, const std::string& format, Args... args) {
std::lock_guard<std::mutex> lock(log_mutex);
auto log_entry = fmt::format("[{}] [{}] {}\n", currentDateTime(), toString(level), fmt::format(format, args...));
log_file << log_entry;
}
protected:
std::ofstream log_file;
mutable std::mutex log_mutex;
const char* toString(LogLevel level) const {
switch(level) {
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
std::string currentDateTime() const {
std::time_t now = std::time(nullptr);
std::string dt = std::ctime(&now);
dt.pop_back(); // 移除换行符
return dt;
}
};
class AsyncLogger : public Logger {
public:
AsyncLogger(const std::string& logFilePath, size_t poolSize = 1)
: Logger(logFilePath), log_pool(poolSize, 1000, async_overflow_policy::block) {}
~AsyncLogger() {
shutdown();
}
void shutdown() {
log_file.close();
}
template <typename... Args>
void log(LogLevel level, const std::string& format, Args... args) {
log_pool.enqueue([this, level, format, args...] {
std::lock_guard<std::mutex> lock(log_mutex);
auto log_entry = fmt::format("[{}] [{}] {}\n", currentDateTime(), toString(level), fmt::format(format, args...));
log_file << log_entry;
});
}
private:
ThreadPool log_pool;
};
int main() {
try {
// 创建同步日志记录器
std::shared_ptr<Logger> syncLogger = std::make_shared<Logger>("sync.log");
// 创建异步日志记录器
std::shared_ptr<AsyncLogger> asyncLogger = std::make_shared<AsyncLogger>("async.log", 4);
// 使用同步日志记录器记录日志
syncLogger->log(Logger::INFO, "这是一条同步日志。");
syncLogger->log(Logger::WARNING, "这是一条同步警告日志。");
syncLogger->log(Logger::ERROR, "这是一条同步错误日志。错误代码:{}。错误信息:{}", 404, "未找到");
// 使用异步日志记录器记录日志
asyncLogger->log(Logger::INFO, "这是一条异步日志。");
asyncLogger->log(Logger::WARNING, "这是一条异步警告日志。");
asyncLogger->log(Logger::ERROR, "这是一条异步错误日志。错误代码:{}。错误信息:{}", 500, "内部服务器错误");
// 模拟程序执行一段时间
std::this_thread::sleep_for(std::chrono::seconds(1));
// 关闭异步日志记录器
asyncLogger->shutdown();
} catch (const std::exception& e) {
std::cerr << "日志记录器初始化失败: " << e.what() << std::endl;
return -1;
}
return 0;
}
在一些项目中,可能会有多个模块或组件需要记录日志。如果每个模块都独立创建和管理自己的日志记录器实例,会导致代码重复和管理困难。此外在程序的不同部分,我们可能需要使用相同的日志记录器实例来记录日志,如果将日志记录器实例作为参数传递给不同的函数或类会让代码变得繁琐和难以维护。这里我们来实现一个类Registry
来实现日志使用单例模式,确保全局只有一个实例,也方便管理和访问不同类型的日志记录器实例。
class Registry {
public:
static Registry& getInstance() {
static Registry instance;
return instance;
}
void registerLogger(const std::string& name, std::shared_ptr<Logger> logger) {
std::lock_guard<std::mutex> lock(mutex_);
loggers_[name] = logger;
}
std::shared_ptr<Logger> getLogger(const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = loggers_.find(name);
if (it != loggers_.end()) {
return it->second;
}
return nullptr;
}
private:
Registry() = default;
~Registry() = default;
Registry(const Registry&) = delete;
Registry& operator=(const Registry&) = delete;
std::unordered_map<std::string, std::shared_ptr<Logger>> loggers_;
std::mutex mutex_;
};
int main() {
try {
// 创建同步日志记录器并注册到 Registry
auto syncLogger = std::make_shared<Logger>("sync.log");
Registry::getInstance().registerLogger("sync", syncLogger);
// 创建异步日志记录器并注册到 Registry
auto asyncLogger = std::make_shared<AsyncLogger>("async.log", 4);
Registry::getInstance().registerLogger("async", asyncLogger);
// 从 Registry 获取同步日志记录器并记录日志
auto sync = Registry::getInstance().getLogger("sync");
sync->log(Logger::INFO, "这是一条同步日志。");
sync->log(Logger::WARNING, "这是一条同步警告日志。");
sync->log(Logger::ERROR, "这是一条同步错误日志。错误代码:{}。错误信息:{}", 404, "未找到");
// 从 Registry 获取异步日志记录器并记录日志
auto async = Registry::getInstance().getLogger("async");
async->log(Logger::INFO, "这是一条异步日志。");
async->log(Logger::WARNING, "这是一条异步警告日志。");
async->log(Logger::ERROR, "这是一条异步错误日志。错误代码:{}。错误信息:{}", 500, "内部服务器错误");
// 模拟程序执行一段时间
std::this_thread::sleep_for(std::chrono::seconds(1));
// 从 Registry 获取异步日志记录器并关闭
auto asyncToShutdown = std::dynamic_pointer_cast<AsyncLogger>(Registry::getInstance().getLogger("async"));
asyncToShutdown->shutdown();
} catch (const std::exception& e) {
std::cerr << "日志记录器初始化失败: " << e.what() << std::endl;
return -1;
}
return 0;
}
第三次优化:创建 sink
我们发现 AsyncLogger 和 Logger 的 log 那部分写重复了,此外,我还想支持输出控制台、文件等模式。这里我们先看下spdlog
怎么实现这个功能的:
class SPDLOG_API async_logger final : public std::enable_shared_from_this<async_logger>,
public logger {
friend class details::thread_pool;
//...
protected:
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
void backend_sink_it_(const details::log_msg &incoming_log_msg);
void backend_flush_();
private:
std::weak_ptr<details::thread_pool> thread_pool_;
async_overflow_policy overflow_policy_;
};
} // namespace spdlog
spdlog
提供四个方法,前两个负责处理消息队列,后两个负责做实际的处理。
// send the log message to the thread pool
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr->post_log( (), msg, overflow_policy_);
}
else {
throw_spdlog_ex("async log: thread pool doesn't exist anymore");
}
}
SPDLOG_LOGGER_CATCH(msg.source)
}
sink_it_
根据阻塞策略来传递消息给消息队列,另外把自己
传递了进去。线程收到消息后,根据操作类型调用自己
的backend_sink_it_
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg,
async_overflow_policy overflow_policy) {
if (overflow_policy == async_overflow_policy::block) {
q_.enqueue(std::move(new_msg));
} else if (overflow_policy == async_overflow_policy::overrun_oldest) {
q_.enqueue_nowait(std::move(new_msg));
} else {
assert(overflow_policy == async_overflow_policy::discard_new);
q_.enqueue_if_have_room(std::move(new_msg));
}
}
void SPDLOG_INLINE thread_pool::worker_loop_() {
while (process_next_msg_()) {
}
}
// process next message in the queue
// return true if this thread should still be active (while no terminate msg
// was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_() {
async_msg incoming_async_msg;
q_.dequeue(incoming_async_msg);
switch (incoming_async_msg.msg_type) {
case async_msg_type::log: {
incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);
return true;
}
case async_msg_type::flush: {
incoming_async_msg.worker_ptr->backend_flush_();
return true;
}
case async_msg_type::terminate: {
return false;
}
default: {
assert(false);
}
}
return true;
}
backend_sink_it_
遍历sinks_
,调用所有的log
,这个sink
又是什么东西呢,它封装了各种输出目的地,比如文件、控制台,提供了一些通用的方法打日志,用户可以通过集成它来实现多输出的打日志。
std::vector<std::shared_ptr<sink>> sinks_;
//
// backend functions - called from the thread pool to do the actual job
//
SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &msg) {
for (auto &sink : sinks_) {
if (sink->should_log(msg.level)) {
SPDLOG_TRY { sink->log(msg); }
SPDLOG_LOGGER_CATCH(msg.source)
}
}
if (should_flush_(msg)) {
backend_flush_();
}
}
class SPDLOG_API sink {
public:
virtual ~sink() = default;
virtual void log(const details::log_msg &msg) = 0;
virtual void flush() = 0;
virtual void set_pattern(const std::string &pattern) = 0;
virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0;
void set_level(level::level_enum log_level);
level::level_enum level() const;
bool should_log(level::level_enum msg_level) const;
protected:
// sink log level - default is all
level_t level_{level::trace};
};
不看细节了,可以看到,spdlog
将输出封装了出来,通过多态来实现不通输出端的日志打印。
我们改下,首先定义一个基类base_sink
:
class base_sink {
public:
virtual ~base_sink() = default;
virtual void log(const std::string& msg) = 0;
virtual void flush() = 0;
};
在定义两个子类,提供两个功能,一个提供日志文本写文件,一个负责彩色输出
class ansicolor_sink : public base_sink {
public:
ansicolor_sink(FILE* file) : file_(file) {}
void log(const std::string& msg) override {
std::lock_guard<std::mutex> lock(mutex_);
fmt::print(file_, "{}", msg);
}
void flush() override {
std::lock_guard<std::mutex> lock(mutex_);
fflush(file_);
}
private:
FILE* file_;
std::mutex mutex_;
};
class file_sink : public base_sink {
public:
file_sink(const std::string& filename) : filename_(filename) {
file_helper_.open(filename, false);
}
void log(const std::string& msg) override {
std::lock_guard<std::mutex> lock(mutex_);
file_helper_.write(msg);
}
void flush() override {
std::lock_guard<std::mutex> lock(mutex_);
file_helper_.flush();
}
private:
std::string filename_;
file_helper file_helper_;
std::mutex mutex_;
};
核心改动就算这些,调度线程池之类的前面都实现了,下面是完整代码:
#include <fstream>
#include <iostream>
#include <string>
#include <ctime>
#include <fmt/core.h>
#include <thread>
#include <chrono>
#include <mutex>
#include <unordered_map>
#include "include/ThreadPool.h"
#include <fstream>
#include <string>
class file_helper {
public:
file_helper() : is_open_(false) {}
void open(const std::string& filename, bool truncate = false) {
std::ios_base::openmode mode = std::ios_base::out | std::ios_base::app;
if (truncate) {
mode |= std::ios_base::trunc;
}
file_stream_.open(filename, mode);
if (!file_stream_.is_open()) {
throw std::runtime_error("无法打开文件:" + filename);
}
is_open_ = true;
}
void write(const std::string& msg) {
if (!is_open_) {
throw std::runtime_error("文件未打开");
}
file_stream_ << msg;
}
void flush() {
if (!is_open_) {
throw std::runtime_error("文件未打开");
}
file_stream_.flush();
}
void close() {
if (is_open_) {
file_stream_.close();
is_open_ = false;
}
}
~file_helper() {
close();
}
private:
std::ofstream file_stream_;
bool is_open_;
};
class base_sink {
public:
virtual ~base_sink() = default;
virtual void log(const std::string& msg) = 0;
virtual void flush() = 0;
};
class ansicolor_sink : public base_sink {
public:
ansicolor_sink(FILE* file) : file_(file) {}
void log(const std::string& msg) override {
std::lock_guard<std::mutex> lock(mutex_);
fmt::print(file_, "{}", msg);
}
void flush() override {
std::lock_guard<std::mutex> lock(mutex_);
fflush(file_);
}
private:
FILE* file_;
std::mutex mutex_;
};
class file_sink : public base_sink {
public:
file_sink(const std::string& filename) : filename_(filename) {
file_helper_.open(filename, false);
}
void log(const std::string& msg) override {
std::lock_guard<std::mutex> lock(mutex_);
file_helper_.write(msg);
}
void flush() override {
std::lock_guard<std::mutex> lock(mutex_);
file_helper_.flush();
}
private:
std::string filename_;
file_helper file_helper_;
std::mutex mutex_;
};
class Logger {
public:
enum LogLevel {
INFO,
WARNING,
ERROR
};
Logger(){}
virtual ~Logger() {
}
void add_sink(std::shared_ptr<base_sink> sink) {
std::lock_guard<std::mutex> lock(sinks_mutex_);
sinks_.push_back(sink);
}
template <typename... Args>
void log(LogLevel level, const std::string& format, Args... args) {
std::lock_guard<std::mutex> lock(log_mutex);
auto log_entry = fmt::format("[{}] [{}] {}\n", currentDateTime(), toString(level), fmt::format(format, args...));
write_to_sinks(log_entry, level);
}
void set_level(LogLevel log_level) {
level_.store(log_level);
}
LogLevel level() const {
return level_.load(std::memory_order_relaxed);
}
protected:
mutable std::mutex log_mutex;
std::vector<std::shared_ptr<base_sink>> sinks_;
std::mutex sinks_mutex_;
std::atomic<LogLevel> level_{LogLevel::INFO};
const char* toString(LogLevel level) const {
switch(level) {
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
std::string currentDateTime() const {
std::time_t now = std::time(nullptr);
std::string dt = std::ctime(&now);
dt.pop_back(); // 移除换行符
return dt;
}
void write_to_sinks(const std::string& log_entry, LogLevel level) {
std::lock_guard<std::mutex> lock(sinks_mutex_);
for (auto& sink : sinks_) {
if (level >= level_) {
sink->log(log_entry);
}
}
}
};
class AsyncLogger : public Logger {
public:
AsyncLogger(size_t poolSize = 1)
: log_pool(poolSize, 1000, async_overflow_policy::block) {}
~AsyncLogger() {
shutdown();
}
void shutdown() {
}
template <typename... Args>
void log(LogLevel level, const std::string& format, Args... args) {
log_pool.enqueue([this, level, format, args...] {
std::lock_guard<std::mutex> lock(log_mutex);
auto log_entry = fmt::format("[{}] [{}] {}\n", currentDateTime(), toString(level), fmt::format(format, args...));
write_to_sinks(log_entry, level);
});
}
private:
ThreadPool log_pool;
};
class Registry {
public:
static Registry& getInstance() {
static Registry instance;
return instance;
}
void registerLogger(const std::string& name, std::shared_ptr<Logger> logger) {
std::lock_guard<std::mutex> lock(mutex_);
loggers_[name] = logger;
}
std::shared_ptr<Logger> getLogger(const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = loggers_.find(name);
if (it != loggers_.end()) {
return it->second;
}
return nullptr;
}
private:
Registry() = default;
~Registry() = default;
Registry(const Registry&) = delete;
Registry& operator=(const Registry&) = delete;
std::unordered_map<std::string, std::shared_ptr<Logger>> loggers_;
std::mutex mutex_;
};
int main() {
try {
// 创建同步日志记录器并注册到 Registry
auto syncLogger = std::make_shared<Logger>();
Registry::getInstance().registerLogger("sync", syncLogger);
// 创建异步日志记录器并注册到 Registry
auto asyncLogger = std::make_shared<AsyncLogger>(4);
Registry::getInstance().registerLogger("async", asyncLogger);
// 创建 sink
auto console_sink = std::make_shared<ansicolor_sink>(stdout);
auto _file_sink = std::make_shared<file_sink>("log.txt");
// 将 sink 添加到同步日志记录器
auto sync = Registry::getInstance().getLogger("sync");
sync->add_sink(console_sink);
sync->add_sink(_file_sink);
// 将 sink 添加到异步日志记录器
auto async = Registry::getInstance().getLogger("async");
async->add_sink(console_sink);
async->add_sink(_file_sink);
// 从 Registry 获取同步日志记录器并记录日志
sync->log(Logger::INFO, "这是一条同步日志。");
sync->log(Logger::WARNING, "这是一条同步警告日志。");
sync->log(Logger::ERROR, "这是一条同步错误日志。错误代码:{}。错误信息:{}", 404, "未找到");
// 从 Registry 获取异步日志记录器并记录日志
async->log(Logger::INFO, "这是一条异步日志。");
async->log(Logger::WARNING, "这是一条异步警告日志。");
async->log(Logger::ERROR, "这是一条异步错误日志。错误代码:{}。错误信息:{}", 500, "内部服务器错误");
// 模拟程序执行一段时间
std::this_thread::sleep_for(std::chrono::seconds(1));
// 从 Registry 获取异步日志记录器并关闭
auto asyncToShutdown = std::dynamic_pointer_cast<AsyncLogger>(Registry::getInstance().getLogger("async"));
asyncToShutdown->shutdown();
} catch (const std::exception& e) {
std::cerr << "日志记录器初始化失败: " << e.what() << std::endl;
return -1;
}
return 0;
}
结果:
[Tue Mar 26 03:35:38 2024] [INFO] 这是一条同步日志。
[Tue Mar 26 03:35:38 2024] [WARNING] 这是一条同步警告日志。
[Tue Mar 26 03:35:38 2024] [ERROR] 这是一条同步错误日志。错误代码:404。错误信息:未找到
[Tue Mar 26 03:35:38 2024] [INFO] 这是一条异步日志。
[Tue Mar 26 03:35:38 2024] [WARNING] 这是一条异步警告日志。
[Tue Mar 26 03:35:38 2024] [ERROR] 这是一条异步错误日志。错误代码:500。错误信息:内部服务器错误
[Tue Mar 26 03:35:38 2024] [INFO] 这是一条同步日志。
[Tue Mar 26 03:35:38 2024] [WARNING] 这是一条同步警告日志。
[Tue Mar 26 03:35:38 2024] [ERROR] 这是一条同步错误日志。错误代码:404。错误信息:未找到
[Tue Mar 26 03:35:38 2024] [INFO] 这是一条异步日志。
[Tue Mar 26 03:35:38 2024] [WARNING] 这是一条异步警告日志。
[Tue Mar 26 03:35:38 2024] [ERROR] 这是一条异步错误日志。错误代码:500。错误信息:内部服务器错误
[!NOTE] 完整代码地址
https://github.com/outmanwt/spdlog-study