CTK入门教程04.03:跨模块日志

本文基于spdlog开发一个可以跨插件模块使用的日志系统,并使用fmt格式化日志输出,支持C/C++使用。

头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#ifndef LOG_H
#define LOG_H

#include <string>

#include "log_global.h"

// 日志级别枚举
enum class LogLevel {
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
CRITICAL,
OFF
};

// C类型接口
extern "C" {
// 初始化日志系统
LOG_EXPORT bool Logger_Initialize(const char* log_dir = "./logs",
const char* log_name = "app",
LogLevel level = LogLevel::INFO);

// 记录日志
LOG_EXPORT void Logger_Trace(const char* fmt, ...);
LOG_EXPORT void Logger_Debug(const char* fmt, ...);
LOG_EXPORT void Logger_Info(const char* fmt, ...);
LOG_EXPORT void Logger_Warn(const char* fmt, ...);
LOG_EXPORT void Logger_Error(const char* fmt, ...);
LOG_EXPORT void Logger_Critical(const char* fmt, ...);

// 工具函数
LOG_EXPORT void Logger_SetLevel(LogLevel level);
LOG_EXPORT void Logger_Flush();
LOG_EXPORT void Logger_Shutdown();
}

// C++ 封装类
class LOG_EXPORT Log {
public:
static Log& GetInstance();

bool Initialize(const std::string& log_dir = "./logs",
const std::string& log_name = "app",
LogLevel level = LogLevel::INFO);

void Trace(const char* fmt, ...);
void Debug(const char* fmt, ...);
void Info(const char* fmt, ...);
void Warn(const char* fmt, ...);
void Error(const char* fmt, ...);
void Critical(const char* fmt, ...);

void SetLevel(LogLevel level);
void Flush();
void Shutdown();

private:
Log();
~Log();
Log(const Log&) = delete;
Log& operator=(const Log&) = delete;
};

#endif // LOG_H

导出函数以便模块外调用。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#include "Log.h"
#include <memory>
#include <string>
#include <iostream>
#include <filesystem>
#include <cstdarg>
#include <vector>

#define SPDLOG_WCHAR_TO_UTF8_SUPPORT
#define SPDLOG_COMPILED_LIB
#include "spdlog/spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"

// 转换LogLevel到spdlog level
spdlog::level::level_enum to_spdlog_level(LogLevel level) {
switch (level) {
case LogLevel::TRACE: return spdlog::level::trace;
case LogLevel::DEBUG: return spdlog::level::debug;
case LogLevel::INFO: return spdlog::level::info;
case LogLevel::WARN: return spdlog::level::warn;
case LogLevel::ERROR: return spdlog::level::err;
case LogLevel::CRITICAL: return spdlog::level::critical;
case LogLevel::OFF: return spdlog::level::off;
default: return spdlog::level::info;
}
}

class LoggerImpl {
public:
LoggerImpl() : initialized(false) {}

bool initialize(const std::string& log_dir, const std::string& log_name, LogLevel level) {
try {
std::filesystem::create_directories(log_dir);

// 创建每日文件sink
auto daily_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(
log_dir + "/" + log_name + ".log", 2, 30);

// 创建控制台sink
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();

// 创建多sink logger
std::vector<spdlog::sink_ptr> sinks {daily_sink, console_sink};
logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());

// 设置日志配置
logger->set_level(to_spdlog_level(level));
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%P] [%t] %v");
logger->flush_on(to_spdlog_level(level));

spdlog::register_logger(logger);
spdlog::set_default_logger(logger);

initialized = true;
return true;
} catch (const std::exception& ex) {
std::cerr << "Logger initialization failed: " << ex.what() << std::endl;
return false;
}
}

void log(spdlog::level::level_enum level, const char* fmt, va_list args) {
if (!initialized) return;

char buffer[4096];
vsnprintf(buffer, sizeof(buffer), fmt, args);
logger->log(level, buffer);
}

void set_level(LogLevel level) {
if (initialized) {
logger->set_level(to_spdlog_level(level));
}
}

void flush() {
if (initialized) {
logger->flush();
}
}

void shutdown() {
if (initialized) {
flush();
spdlog::drop_all();
initialized = false;
}
}

bool is_initialized() const { return initialized; }

private:
std::shared_ptr<spdlog::logger> logger;
bool initialized;
};

// 全局实例
static LoggerImpl g_logger;

// C接口实现
LOG_EXPORT bool Logger_Initialize(const char* log_dir, const char* log_name, LogLevel level) {
return g_logger.initialize(log_dir ? log_dir : "./logs",
log_name ? log_name : "app",
level);
}

LOG_EXPORT void Logger_Trace(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::trace, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_Debug(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::debug, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_Info(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::info, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_Warn(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::warn, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_Error(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::err, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_Critical(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::critical, fmt, args);
va_end(args);
}

LOG_EXPORT void Logger_SetLevel(LogLevel level) {
g_logger.set_level(level);
}

LOG_EXPORT void Logger_Flush() {
g_logger.flush();
}

LOG_EXPORT void Logger_Shutdown() {
g_logger.shutdown();
}

// C++ 类实现
Log& Log::GetInstance() {
static Log instance;
return instance;
}

Log::Log() {}
Log::~Log() { Shutdown(); }

bool Log::Initialize(const std::string& log_dir, const std::string& log_name, LogLevel level) {
return Logger_Initialize(log_dir.c_str(), log_name.c_str(), level);
}

void Log::Trace(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::trace, fmt, args);
va_end(args);
}

void Log::Debug(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::debug, fmt, args);
va_end(args);
}

void Log::Info(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::info, fmt, args);
va_end(args);
}

void Log::Warn(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::warn, fmt, args);
va_end(args);
}

void Log::Error(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::err, fmt, args);
va_end(args);
}

void Log::Critical(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
g_logger.log(spdlog::level::critical, fmt, args);
va_end(args);
}

void Log::SetLevel(LogLevel level) {
g_logger.set_level(level);
}

void Log::Flush() {
g_logger.flush();
}

void Log::Shutdown() {
g_logger.shutdown();
}

// int main() {
// // 方式1: 使用C接口
// Logger_Initialize("./logs", "myapp", LogLevel::DEBUG);

// Logger_Info("应用程序启动 - C接口");
// Logger_Debug("调试信息: %s, 数值: %d", "测试", 42);
// Logger_Error("错误信息: 文件 %s 不存在", "config.ini");

// // 方式2: 使用C++类接口
// Logger::GetInstance().Initialize("./logs", "myapp_cpp", LogLevel::DEBUG);
// Logger::GetInstance().Info("C++接口日志记录");
// Logger::GetInstance().Debug("格式化消息: %s %f", "测试", 3.14);

// Logger_Shutdown();
// return 0;
// }

先实现C接口,C++接口是C接口的封装。

插件

只是普通的dll文件,并非ctk插件,各插件都可以调用。


CTK入门教程04.03:跨模块日志
https://blog.jackeylea.com/ctk/cross-dll-log/
作者
JackeyLea
发布于
2025年10月27日
许可协议