日志
1. 为什么需要日志等级?
在实际生产中,程序输出的信息非常多,如果没有等级就会导致:
- 开发阶段找不到重点(调试信息太多)。
- 上线后也不好排查问题(没有区分严重错误和普通信息)。
因此,合理使用日志等级,能让我们:
- 快速定位错误。
- 过滤无用信息。
- 分环境(开发、测试、生产)灵活控制日志量。
2. Linux 常见日志等级
以 syslog
标准 为例(这是 Linux 内核和很多守护进程默认遵循的):
等级名 |
数值(优先级) |
典型含义 |
EMERG |
0 |
系统不可用,比如内核崩溃(panic) |
ALERT |
1 |
必须立刻采取措施,比如磁盘坏块 |
CRIT |
2 |
严重错误,可能导致程序崩溃 |
ERR (ERROR) |
3 |
一般错误,需要修复 |
WARNING |
4 |
警告信息,可能有潜在风险 |
NOTICE |
5 |
正常但需要注意的事件 |
INFO |
6 |
普通运行信息 |
DEBUG |
7 |
调试信息,开发阶段最详细 |
- 数值越小,级别越高,越重要。
- 在生产环境中,一般只保留 WARNING 及以上等级,避免刷盘压力和磁盘占用。
然而在实际操作当中,我们大多只考虑下面几种日志等级信息:
- Info:常规消息。
- Warning:报警信息。
- Error:比较严重了,可能需要立即处理。
- Fatal:致命的。
- Debug:调试信息。
无论是写到文件、控制台,还是系统日志,一个完整的日志条目 通常包含:
部分 |
示例 |
说明 |
时间戳(必须) |
2025-01-01 11:11:11 |
发生时间 |
日志等级(必须) |
INFO |
该条日志的重要性 |
内容(必须) |
Connection established from 192.168.225.225.0 |
实际信息 |
主机名 |
server-01 |
哪台机器 |
程序名/模块名 |
nginx |
哪个程序 |
进程 ID |
[pid=1234] |
哪个进程 |
线程 ID |
[tid=5678] |
哪个线程(多线程时可选) |
上下文(可选) |
文件名、行号、函数名 |
开启调试时很重要 |
3. 日志的实现
下面演示之前的 有名管道通信的服务端-客户端模型 Demo 进行的日志化:
1. comm.hpp 文件
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
| #pragma once
#include <iostream> #include <string> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include "Log.hpp" using namespace std;
#define FIFO_FILE "./myfifo" #define MODE 0664
class Init { public: Init() { int n = mkfifo(FIFO_FILE, MODE);
if (n == -1) { perror("mkfifo"); exit(FIFO_CREATE_ERR); } }
~Init() { int m = unlink(FIFO_FILE); if (m == -1) { perror("unlink"); exit(FIFO_DELETE_ERR); } } };
|
2. Log.hpp(主要)
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
| #pragma once
#include <iostream> #include <string> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <sys/time.h> #include <ctime> using namespace std;
enum FIFO_ERROR_CODE { FIFO_CREATE_ERR = 1, FIFO_DELETE_ERR = 2, FIFO_OPEN_ERR };
enum Log_Level { Fatal, Error, Warning, Debug, Info };
class Log { int enable = 1; int classification = 1; string log_path = "./log.txt"; int console_out = 1;
string level_to_string(int level) { switch (level) { case Fatal: return "Fatal"; case Error: return "Error"; case Warning: return "Warning"; case Debug: return "Debug"; case Info: return "Info"; default: return "None"; } }
string get_current_time() { struct timeval tv; gettimeofday(&tv, nullptr);
struct tm t; localtime_r(&tv.tv_sec, &t);
char buffer[64];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d.%06ld", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, tv.tv_usec);
return string(buffer); }
public: Log() = default; Log(int enable, int classification, string log_path, int console_out) : enable(enable), classification(classification), log_path(log_path), console_out(console_out) {
} void operator()(int level, const string& content) { if (enable == 0) { return; }
string level_str = "[" + level_to_string(level) + "] "; string log_message;
if (classification == 1) { log_message = level_str + "[" + get_current_time() + "] " + content + "\n"; } else if (classification == 0) { log_message = "[" + get_current_time() + "] " + content + "\n"; } else { printf("传入的分类参数错误!\n"); return; }
if (console_out == 1) { cout << log_message; }
log_to_file(level, log_message); }
private: string Suffix_processing(int level, string log_path) { string Path; if (log_path.back() == '/') { Path = log_path + "log_" + level_to_string(level) + ".txt"; } else { size_t pos = log_path.find_last_of('.'); if (pos != string::npos) { string left = log_path.substr(0, pos); string right = log_path.substr(pos); Path = left + "_" + level_to_string(level) + right; } else { Path = log_path + "_" + level_to_string(level) + ".txt"; } }
return Path; }
void log_to_file(int level, const string& log_content) { string Path;
if (classification == 1) { Path = Suffix_processing(level, log_path); } else if (classification == 0) { Path = log_path; }
int fd = open(Path.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd < 0) { perror(""); exit(FIFO_OPEN_ERR); }
write(fd, log_content.c_str(), log_content.size()); close(fd); } };
|
3. Client.cc 文件
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
| #include "comm.hpp"
int main() { Log log(1, 1, "./log.txt", 1); int fd = open(FIFO_FILE, O_WRONLY); if(fd < 0) { perror("open"); log(Error, "open FIFO_FILE failed"); exit(FIFO_OPEN_ERR); }
string str; while(true) { cout << "请输入要发送的消息:"; getline(cin, str);
write(fd, str.c_str(), str.size()); log(Info, "发送的消息: " + str); } close(fd); return 0; }
|
4. Server.cc 文件
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
| #include "comm.hpp"
int main() { Log log(1, 0, "./log.txt", 1);
int fd = open(FIFO_FILE, O_RDONLY); if(fd < 0) { perror("open"); log(Error, "打开文件失败!"); exit(FIFO_OPEN_ERR); }
while(true) { char buf[1024] = {0}; int x = read(fd, buf, sizeof(buf)); if(x > 0) { buf[x] = 0; cout << "客户端说:" << buf << endl; log(Debug, "收到消息: " + (string)buf); } }
close(fd);
return 0; }
|
5. 运行示例
日志插件 Demo 展示