当前位置: 首页 > news >正文

033 日志

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

  • 日志
    • 1. 为什么需要日志等级?
    • 2. Linux 常见日志等级
    • 3. 日志的实现
      • 1. comm.hpp 文件
      • 2. Log.hpp(主要)
      • 3. Client.cc 文件
      • 4. Server.cc 文件
      • 5. 运行示例
    • 共勉

日志

1. 为什么需要日志等级?

在实际生产中,程序输出的信息非常多,如果没有等级就会导致:

  • 开发阶段找不到重点(调试信息太多)。
  • 上线后也不好排查问题(没有区分严重错误和普通信息)。

因此,合理使用日志等级,能让我们:

  1. 快速定位错误。
  2. 过滤无用信息。
  3. 分环境(开发、测试、生产)灵活控制日志量。

2. Linux 常见日志等级

syslog 标准 为例(这是 Linux 内核和很多守护进程默认遵循的):

等级名数值(优先级)典型含义
EMERG0系统不可用,比如内核崩溃(panic)
ALERT1必须立刻采取措施,比如磁盘坏块
CRIT2严重错误,可能导致程序崩溃
ERR (ERROR)3一般错误,需要修复
WARNING4警告信息,可能有潜在风险
NOTICE5正常但需要注意的事件
INFO6普通运行信息
DEBUG7调试信息,开发阶段最详细
  • 数值越小,级别越高,越重要。
  • 在生产环境中,一般只保留 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 文件

#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// enum
// {
//     FIFO_CREATE_ERR = 1,                    // 这是创建管道文件失败的错误码
//     FIFO_DELETE_ERR = 2,                    // 这是删除管道文件失败的错误码
//     FIFO_OPEN_ERR                           // 这是打开管道文件失败的错误码(枚举会自动赋值为3)
// };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(主要)

#pragma once#include <iostream>
#include <string>
#include <stdlib.h>                                      // exit, perror
#include <unistd.h>                                      // read, write, close
#include <sys/types.h>                                   // open, close, read, write, lseek
#include <sys/stat.h>                                    // mkdir
#include <fcntl.h>                                       // open, O_RDONLY, O_WRONLY, O_CREAT, O_APPEND
#include <errno.h>                                       // errno
#include <sys/time.h>                                    // gettimeofday, struct timeval
#include <ctime>                                         // localtime_r, struct tm
using namespace std;// 管道错误码
enum FIFO_ERROR_CODE
{FIFO_CREATE_ERR = 1,                                 // 这是创建管道文件失败的错误码FIFO_DELETE_ERR = 2,                                 // 这是删除管道文件失败的错误码FIFO_OPEN_ERR                                        // 这是打开管道文件失败的错误码(枚举会自动赋值为3)
};// 日志等级
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";}}// 获取当前计算机的时间,返回格式:YYYY-MM-DD HH:MM:SS.UUUUUU (含微秒)string get_current_time(){struct timeval tv;                               // timeval:包含秒和微秒gettimeofday(&tv, nullptr);                      // 系统调用:获取当前时间(精确到微秒)struct tm t;                                     // tm:分解时间,转格式(年、月、日、时、分、秒)localtime_r(&tv.tv_sec, &t);                     // 把秒转换成年月日时分秒(本地时区)char buffer[64];                                 // 定义字符数组作为格式化输出的缓冲区snprintf(buffer, sizeof(buffer),"%04d-%02d-%02d %02d:%02d:%02d.%06ld",t.tm_year + 1900,                        // 年:tm_year 从 1900 开始计数t.tm_mon + 1,                            // 月:tm_mon 从 0 开始,0 表示 1 月t.tm_mday,                               // 日t.tm_hour,                               // 时t.tm_min,                                // 分t.tm_sec,                                // 秒tv.tv_usec);                             // 微秒部分,取自 gettimeofdayreturn string(buffer);                           // 转换成 string 返回}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:// 文件路径的后缀处理函数:当按照日志等级分类存储并且文件路径是 "./log.txt" 这种有文件扩展名时的处理方法string Suffix_processing(int level, string log_path){string Path;if (log_path.back() == '/')                      // 如果是一个目录的路径,比如 "./log/",则最终文件名为 "log_等级名.txt"{Path = log_path + "log_" + level_to_string(level) + ".txt";}else                                             // 如果是一个文件路径,比如 "./log.txt",则最终文件名为 "log_等级名.txt"{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                                         // 如果没有文件扩展名(比如 "./log"),则直接在文件名后面加上 "_等级名.txt"{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;                             // 不分类直接使用传入的 log_path}// 追加写入,文件不存在则创建,权限 0644int 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 文件

#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());         // 向管道文件写入消息,str.c_str()是 string 转换为 C 风格字符串 的方法log(Info, "发送的消息: " + str);             // 记录发送内容}close(fd);                                      // 关闭管道文件return 0;
}

4. Server.cc 文件

#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));             // 读取管道数据,x 为读取的字节数if(x > 0){buf[x] = 0;                                 // 将完整的字节流转换为字符串,并添加结束符‘\0’cout << "客户端说:" << buf << endl;log(Debug, "收到消息: " + (string)buf);      // 记录接收内容}}close(fd);                                          // 关闭管道return 0;
}

5. 运行示例

日志插件 Demo 展示

共勉

在这里插入图片描述
在这里插入图片描述

http://www.dtcms.com/a/355433.html

相关文章:

  • 硬件三人行--运算基础篇
  • 怎样将Word转成高质量的DITA
  • 【涂鸦T5】1. 环境搭建和demo
  • 量化策略布林带解读
  • Java Spring(1)- Spring基础
  • AI提升SEO关键词效果新策略
  • PostgreSQL【应用 04】加解密扩展 pgcrypto 使用实例(加密、导出、导入、解密流程说明)
  • 信息技术发展
  • Flink Redis广播方案
  • 深度学习④【经典卷积神经网络演进:从LeNet到ResNet(重要意义)的架构革命】
  • Uniapp中自定义导航栏
  • 使用qianjkun uniapp 主应用 集成 vue微应用
  • Android 使用MediaMuxer+MediaCodec编码MP4视频
  • 把 AI 塞进「智能手环」——基于心率变异的零样本压力监测手环
  • sqlserver: count(*)
  • TCP和HTTP的keep-alive的区别
  • 嵌入式第四十天(TCP并发服务端(IO多路复用))
  • 【Python 入门】(1)Python 语言基础(语法特点)
  • OSI模型和TCP/IP模型区别是什么
  • JAVA全栈Redis篇————Redis常用数据类型概述
  • 如何快速copy复制一个网站,或是将网站本地静态化访问
  • 电力电子中的变压器原理、作用、选型与测量指南-超简单解读
  • 雷达传感器和红外传感器的区别
  • MCP tutorials
  • HOOPS Communicator 2025.6.0更新发布:WebViewer UI全面进化,BIM支持再升级
  • C++(Qt)软件调试---vspkg安装crashpad(34)
  • 浅谈 Java 中的 import static 使用方式
  • Docker 是什么?
  • RabbitMQ-高级特性
  • 机器视觉学习-day09-图像矫正