在实际生产中,日志是非常重要的调试工具,日志内容至少需要包括时间戳、日志级别、日志内容
推荐的日志库有:
google/glog: C implementation of the Google logging module (github.com)
Apache Log4cxx: Apache Log4cxx
自己实现的话, 日志内容应该包括,精确到微秒的时间戳,日志级别(DEBUG / INFO / WARN / ERROR / FATAL),日志写入时的代码文件名,代码行号和函数名,例如
代码语言:javascript复制2024-05-25 23:46:07.998429 [FATAL] This is a fatal message (File=C:/Users/Yezi/Desktop/Logger/main.cpp Function=main Line=9)
并且我希望日志是这样使用的
代码语言:javascript复制#include "logger.h"
int main() {
Logger::InitLogger("logfile.txt");
LOG(LogLevel::DEBUG, "This is a debug message");
LOG(LogLevel::INFO, "This is an info message");
LOG(LogLevel::WARN, "This is a warning message");
LOG(LogLevel::ERROR, "This is an error message");
LOG(LogLevel::FATAL, "This is a fatal message");
return 0;
}
而不是这样使用的
代码语言:javascript复制int main() {
Logger logger("logfile.txt");
// 示例使用
LOG(logger, LogLevel::DEBUG, "This is a debug message");
LOG(logger, LogLevel::INFO, "This is an info message");
LOG(logger, LogLevel::WARN, "This is a warning message");
LOG(logger, LogLevel::ERROR, "This is an error message");
LOG(logger, LogLevel::FATAL, "This is a fatal message");
return 0;
}
这意味着我们需要一个单例模式的实现,需要将类实例静态化,由一个静态函数返回类实例的引用,由于静态变量只会初始化一次,所以每次返回的都是同一个实例 同时我们希望能够保留可以更改类实例初始化的参数,例如日志文件名,因此需要一个初始化的静态函数来进行类实例的初始化
代码语言:javascript复制//
// Created by YEZI on 2024/5/25.
//
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
// 定义日志级别
enum class LogLevel { DEBUG, INFO, WARN, ERROR, FATAL };
class Logger {
private:
int fd_ = -1;
// 获取当前时间戳
static std::string getCurrentTime() {
// 获取当前时间点
auto now = std::chrono::system_clock::now();
// 将时间点转换为time_t
auto now_time_t = std::chrono::system_clock::to_time_t(now);
// 获取tm结构体
std::tm time_info = *std::localtime(&now_time_t);
// 构造时间字符串
std::ostringstream oss;
oss << std::put_time(&time_info, "%Y-%m-%d %H:%M:%S"); // 格式化时间
// 获取微秒部分
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(
now.time_since_epoch()) % 1000000;
oss << "." << std::setfill('0') << std::setw(6) << microseconds.count(); // 微秒部分
return oss.str();
}
// 私有化构造函数
explicit Logger(const char *filename) {
// 打开日志文件
fd_ = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd_ == -1) {
std::cerr << "Failed to open log filen";
}
}
public:
~Logger() {
// 关闭日志文件
if (fd_ != -1) {
close(fd_);
}
}
//初始化日志文件
static void InitLogger(const char *filename) {
getInstance(filename);
}
// 禁用拷贝构造函数和赋值运算符
Logger(const Logger &) = delete;
Logger &operator=(const Logger &) = delete;
// 获取 Logger 实例的静态方法
static Logger &getInstance(const char *filename = nullptr) {
// 静态变量只会初始化一次
static Logger instance(filename);
return instance;
}
// 写日志函数
void log(LogLevel level, const char *message, const char *file, int line, const char *function) const {
std::string logLevelStr;
switch (level) {
case LogLevel::DEBUG:
logLevelStr = "DEBUG";
break;
case LogLevel::INFO:
logLevelStr = "INFO";
break;
case LogLevel::WARN:
logLevelStr = "WARN";
break;
case LogLevel::ERROR:
logLevelStr = "ERROR";
break;
case LogLevel::FATAL:
logLevelStr = "FATAL";
break;
}
// 构建日志消息
std::string logMessage = getCurrentTime() " [" logLevelStr "] " message
" (File=" file " Function=" function " Line=" std::to_string(line) ")n";
// 写入日志到文件
write(fd_, logMessage.c_str(), logMessage.size());
}
};
// 宏定义简化日志调用
#define LOG(level, message) Logger::getInstance().log(level, message, __FILE__, __LINE__, __FUNCTION__)
#endif //LOGGER_H
代码维护在GitHub
MaolinYe/Logger: C 实现的日志类,记录日志写入时的时间,可选的日志级别(DEBUG / INFO / WARN / ERROR / FATAL),日志内容,日志写入时的代码文件,代码行号和函数名 (github.com)