Linux之日志

日志

在编写网络服务器, 各种软件时, 程序一定要打印一些日志信息.

1. 可以向显示器打印, 也可以向文件中写入.

2. 日志是软件在运行时记录的流水账, 用于排查服务进程挂掉的信息.

  • 其中必须要有的是: 日志等级, 时间, 日志内容.
  • 可选的是文件名, 代码行数, 进程pid 等

日志等级可以根据需要设置, 暂时先设置5个: 

enum LogLevel
{Debug = 0,Info,Warning,Error,Fatal
};

我们的日志输出方式可以分为3类: 显示器打印, 不分类写入一个文件, 根据日志等级分类写入不同文件

enum OutPutStyle
{SCREEN = 0,//显示器打印ONEFILE,//输入到一个文件CLASSFILES//分类输出
};

 我们可以设置:

  • 默认的输出方式是SCREEN
  • 默认的日志文件目录名称为log
  • 默认日志文件名为log.xxx, 后缀后面再详细设置

可以利用mkdir接口创建目录:  

const OutPutStyle defaultStyle = SCREEN;
const std::string defaultFileName = "log.";
const std::string defaultDirName = "log";class Log
{
public:Log(OutPutStyle style = defaultStyle): _style(style){if(style != SCREEN)mkdir(defaultDirName.c_str(), 0777);}
private:OutPutStyle _style;
};

然后我们要完成日志的主体工作:

void LogMessage(LogLevel level, const char *format, ...){char headBuffer[1024] = {0};sprintf(headBuffer, "[%s][%s][%d]: ", LevelToString(level).c_str(), TimeToString().c_str(), getpid());char contentBuffer[1024] = {0};va_list arg;va_start(arg, format);vsprintf(contentBuffer, format, arg);va_end(arg);std::string message = std::string(headBuffer) + std::string(contentBuffer);switch (_style){case SCREEN:WriteToScreen(message);break;case ONEFILE:WriteToFile("all", message);break;case CLASSFILES:WriteToClassFILE(LevelToString(level), message);break;default:std::cout << "Unknown Style" <<std::endl;break;}}

 LogMessage函数就是为了将日志的内容根据日志的 style 输出.

一. 由于我们并不确定每次要向日志输出的内容, 所以用一个可变参数列表来模仿 printf 形式的输出格式:

1. 首先headBuffer是我们必带的属性: 日志等级, 时间, 和进程的pid;

2. 然后利用通过可变参数相关的宏和vsprintf把内容输出到contenBuffer中, 用法类似sprintf, 只是最后一个参数的变化.

 3. 然后就得到了我们所需的完整的message.

关于vsprintf的接口: 

 二. 接着根据需要的输出风格把日志内容输出即可, 这里我们封装三个函数, 分别是WriteToScreen, WriteToFile, WriteToClassFILE:

void WriteToScreen(const std::string& message)
{std::cout << message << std::endl;
}void WriteToFile(const std::string& levelstr, const std::string& message)
{std::string filename = defaultDirName + "/" + defaultFileName + levelstr;std::ofstream os(filename.c_str(), std::ofstream::out | std::ofstream::app);if(!os.is_open()){std::cout << "open error" << std::endl;exit(1);}os << message << std::endl;os.close();
}void WriteToClassFILE(const std::string& levelstr, const std::string& message)
{WriteToFile(levelstr, message);
}

 1. 输出到屏幕直接输出即可.

2. 输出到文件需要通过日志等级确定文件的后缀名, 所以多加一个参数.

三. 记得添加需要的两个功能函数, 将日志等级转换为字符串和获取时间

std::string LevelToString(LogLevel level){switch (level){case Debug:return "Debug";break;case Info:return "Info";break;case Warning:return "Warning";break;case Error:return "Error";break;case Fatal:return "Fatal";break;default:return "Unknown";break;}}std::string TimeToString(){time_t time = std::time(nullptr);char timeString[] = "yyyy-mm-ddThh:mm:ssZ";//std::cout << typeid(std::data(timeString)).name() << std::endl;std::strftime(timeString, sizeof(timeString), "%F %T", std::localtime(&time));return timeString;}

这样日志就设计完成. 测试一下, 这里我们选择分类输出:

#include "log.hpp"int main()
{Log lg(CLASSFILES);lg.LogMessage(Debug, "this is a log message %d %s", 42, "hello world");lg.LogMessage(Info, "this is a log message %d %s", 42, "hello world");lg.LogMessage(Warning, "this is a log message %d %s", 42, "hello world");lg.LogMessage(Error, "this is a log message %d %s", 42, "hello world");lg.LogMessage(Fatal, "this is a log message %d %s", 42, "hello world");return 0;
}

 运行之后目录下多出一个log文件夹, 里面记录了不同日记等级的日志

查看一下内容: 

补充:

logMessage部分我们也可以用C++11的可变参数列表来实现:

template<class T>void BuildContent(std::ostringstream& oss, T head) {oss << head;}template<class T, class ...Args>void BuildContent(std::ostringstream& oss, T head, Args... rest) {oss << head << " "; // 每个参数用空格分隔BuildContent(oss, rest...);}template<class T, class ...Args>void LogMessage(LogLevel level, T head, Args... rest){char headBuffer[1024] = {0};sprintf(headBuffer, "[%s][%s][%d]: ", LevelToString(level).c_str(), TimeToString().c_str(), getpid());std::ostringstream oss;BuildContent(oss, head, rest...); std::string contentBuffer = oss.str();std::string message = std::string(headBuffer) + contentBuffer;switch (_style){case SCREEN:WriteToScreen(message);break;case ONEFILE:WriteToFile("all", message);break;case CLASSFILES:WriteToClassFILE(LevelToString(level), message);break;default:std::cout << "Unknown Style" <<std::endl;break;}}

 不过这样我们在使用logMessage函数时就要注意一下格式, 我们使用了ostringstream, 所以使用的方式类似于流输出的形式, 此外我们还默认给每个参数之间添加了空格分隔.

int main()
{Log lg(CLASSFILES);lg.LogMessage(Debug, "this is a log message", 42, "hello world");lg.LogMessage(Info, "this is a log message", 42, "hello world");lg.LogMessage(Warning, "this is a log messag", 42, "hello world");lg.LogMessage(Error, "this is a log message", 42, "hello world");lg.LogMessage(Fatal, "this is a log message", 42, "hello world");return 0;
}

 输出结果和之前是一样的.


关于可变参数列表

这是关于C语言的可变参数列表的介绍, 可以选择性观看.

引例

求两个数最大值

int FindMax(int x,int y) { if(x>y){ return x; } return y; }

首先明确三个问题

1.如果函数没有形式参数, 我们也可以给函数传递参数

2.在C中, 只要发生了函数调用并且传递了参数, 必定形成临时变量

3.所谓的临时拷贝本质就是在当前函数栈帧内部形成的数据, 且顺序是从右到左依次形成临时拷贝.

现在我们想要: 求任意多个数据中的最大值(至少一个), 且要求不能使用数组.

因为目前参数个数不确定,那么函数编写的时候, 参数个数也无法确定, 换句话说, 函数也就没法编写, 不过, C提供了满足该场景的解决方案: 可变参数列表

先了解如何使用可变参数列表:

int FindMax(int num,...)//使用可变参数的要求,至少使用一个明确的参数
{va_list arg;//定义可以访问可变参数部分的变量,其实是一个char*类型va_start(arg,num); //使arg指向可变参数部分int max = va_arg(arg,int);//根据类型,获取可变参数列表中的第一个数据for(int i =0;i<num;i++)//获取并比较其他的{int cur = va_arg(arg,int);if(max<cur){max = cur;        }    }va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULLreturn max;
}
int main()
{int max = FindMax(5,11,22,33,44,55);printf("max = %d\n", max);return 0;
}

如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?

int FindMax(int num, ...)
{va_list arg;     //char*va_start(arg, num); int max = va_arg(arg, int);//va_arg(arg,char);是不正确的for (int i = 0; i < num - 1; i++){//获取并比较其他的int curr = va_arg(arg, int);if (max < curr){max = curr;}}va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULLreturn max;
}
int main()
{char a = '1'; //ascii值: 49char b = '2'; //ascii值: 50char c = '3'; //ascii值: 51char d = '4'; //ascii值: 52char e = '5'; //ascii值: 53int max = FindMax(5, a, b, c, d, e);printf("max = %d\n", max);return 0;
}
//结果并未受影响,可是,我们解析的时候,是按照va_arg(arg, int)来解析的,这是为什么?

通过查看汇编,我们看到,在可变参数场景下:

实际传入的参数如果是char, short, float, 编译器在编译的时候, 会自动进行提升(通过查看汇编,我们都能看到), movsx 把值进行带符号扩展

函数内部使用的时候,根据类型提取数据,一般是通过int或者double来进行

注意事项

1. 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数那是不行的

2. 参数列表中至少有一个命名参数. 如果连一个命名参数都没有, 就无法使用 va_start, 因为根本找不到可变参数的起始位置.

3. 这些宏是无法直接判断实际存在参数的数量。//printf在第一个参数的格式化字符串中的%可以判定有几个参数

4. 这些宏无法判断每个参数的类型

5. 如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。

可变参数的原理:

可变参数列表对应的函数, 最终调用也是函数调用, 也要形成栈帧. 栈帧形成前, 临时变量是要先入栈的, 我们知道参数之间位置关系相对固定

通过上面汇编的查看,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确.

几个宏的含义:

1. va_list;

va_list其实就是char*类型,方便后续按照字节进行指针移动

 2. va_start, va_arg , va_end 

3. 核心工作是三个宏的定义

#define _crt_va_start(ap, v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

举例:arg = (char*)&num + 4

#define _crt_va_arg(ap, t)  ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

1. 把“当前元素”提取出来

2. arg指向下一个待访问元素

#define _crt_va_end(ap)   ( ap = (va_list)0 )

指针归零,防止野指针

其中: #define _ADDRESSOF(v)  ( &(v) ) 是取参数的地址, 很好理解

关键是对于偏移量的计算, 如何正确拿到下一个参数的大小?

#define _INTSIZEOF(n)  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) //难点!!

_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式:

 比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4

 比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8

第一步理解:4的倍数

 既然是4的最小整数倍取整,那么本质是:x=4*m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8 而m具体是多少,取决于n是多少 

 如果n能整除4,那么m就是n/4

 如果n不能整除4,那么m就是n/4+1

 上面是两种情况,如何合并成为一种写法呢? 

 常见做法是 ( n+sizeof(int)-1) )/sizeof(int) -> (n+4-1)/4

如果n能整除4,那么m就是(n+4-1)/4->(n+3)/4, +3的值无意义,会因取整自动消除,等价于 n/4.

如果n不能整除4,那么n=最大能整除4部分+r,1(能整除4部分+r+3)/4,其中4 能整除4部分/4 + (r+3)/4 -> n/4+1

第二步理解:最小4字节对齐数

 搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了

((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/4)*4

 这样就能求出来4字节对齐的数据了,其实上面的写法,在功能上,已经和源代码中的宏等价了。

第三步理解:理解源代码中的宏

 拿出简洁写法:((n+4-1)/4)* 4,设w=n+4-1, 那么表达式可以变化成为 (w/4)*4, 而4就是2^2,w/4,不就相当于右移两位吗? 再除4不就相当左移两位吗?先右移两位,在左移两位,最终结果就是, 最后2个比特位被清空为0!

所以(w/4)*4等价于w & ~3

 所以, 简洁版:(n+4-1) & ~(4-1)

 所以最终得到了源码版的_INTSIZEOF(n): ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1 ) ) 无需先/,再*

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/60674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA指定Maven的settings不生效问题处理

文章目录 一、问题描述二、问题分析三、问题解决 一、问题描述 在Idea中手动指定了maven的settings配置文件&#xff0c;但是一直没生效。 如下图&#xff1a;设置加载settings-aliyun.xml文件&#xff0c;但是最后发现还是在加载settings.xml文件 二、问题分析 ‌在Intel…

【软考】数据库

1. 数据模型 1.1 概念数据模型 概念数据模型一般用 E-R 图表示&#xff0c;常用术语如下&#xff1a; 实体&#xff1a;客观存在的事物&#xff0c;如&#xff1a;一个单位、一个职工、一个部门、一个项目。属性&#xff1a;学生实体有学号、姓名、出生日期等属性。码&#…

oneplus6线刷、trwp、magisk(apatch)、LSPosed、Shamiko、Hide My Applist

oneplus6线刷android10.0.1 oneplus6线刷包(官方android10.0.1)下载、线刷教程&#xff1a; OnePlus6-brick-enchilada_22_K_52_210716_repack-HOS-10_0_11-zip 启用开发者模式 设置 / 连续点击6次版本号 : 启用开发者模式设置/开发者模式/{打开 usb调试, 打开 网络adb调试,…

ByteBuffer模拟拆包输出消息字符串

以下代码模拟网络编程中的粘包现象&#xff0c;用\n进行分割消息块 源码 public static void main(String[] args) {ByteBuffer byteBuffer1 ByteBuffer.allocate(60) ;byteBuffer1.put("Hello World\nWhat is you name?\nI am Licky!\nHo".getBytes());splice(byt…

成都睿明智科技有限公司怎么样可靠不?

在这个日新月异的数字时代&#xff0c;电商行业如同一股不可阻挡的洪流&#xff0c;席卷着每一个消费者的生活。而抖音&#xff0c;作为短视频与电商完美融合的典范&#xff0c;更是为无数商家开辟了一片全新的蓝海。在这片充满机遇与挑战的海洋中&#xff0c;成都睿明智科技有…

【计算机网络】多路转接之epoll

epoll也是一种linux中的多路转接方案(epoll也是只负责IO过程中的"等") 一、epoll相关接口的使用 1.epoll_create int epoll_create(int size); ​功能&#xff1a;创建一个epoll模型 ① int size&#xff1a;没意义了 >0就行 返回值&#xff1a;返回一个文件…

Linux高阶——1117—TCP客户端服务端

目录 1、sock.h socket常用函数 网络初始化函数 首次响应函数 测试IO处理函数 获取时间函数 总代码 2、sock.c SOCKET() ACCEPT()——服务端使用这个函数等待客户端连接 CONNECT()——客户端使用这个函数连接服务端 BIND()——一般只有服务端使用 LISTEN()——服务端…

【SVN和GIT】版本控制系统详细下载使用教程

文章目录 ** 参考文章一、什么是SVN和GIT二、软件使用介绍1 SVN安装1.1 服务端SVN下载地址1.2 客户端SVN下载地址2 SVN使用2.1 服务端SVN基础使用2.1.1 创建存储库和用户成员2.1.2 为存储库添加访问人员2.2 客户端SVN基础使用2.2.1 在本地下载库中的内容2.2.2 版本文件操作--更…

【含文档】基于django+Vue的荣誉证书管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 主要技术: django,mysql,vue 2.视频演示地址 3.功能 系统定义了三个角色&#xff1a;管理员和学生和教师。 管理员进…

ros2学习日记_241124_ros相关链接

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

AIGC-------AIGC在社交媒体内容生成中的应用

AIGC在社交媒体内容生成中的应用 引言 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;社交媒体平台上的内容创作方式发生了巨大变化。AIGC使得内容创作的门槛大大降低&#xff0c;从而让更多的人能够参与到社交媒体内容的创作中&#xff0c;同时也使…

GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解

GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解 目录 GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 GWO-SVMD灰狼算法优化逐次变分模态分解 内有15种用以优化svmd的适应度函数&#…

意识与人工智能:德国语言学家Joscha Bach的“梦境意识”理论探讨

引言 在人类的科学探索中&#xff0c;意识无疑是最深奥的未解之谜之一。尽管我们可以清晰地感知到自己的存在和思维&#xff0c;但意识究竟是什么&#xff1f;它从何而来&#xff1f;是否是物理世界的产物&#xff1f;以及人工智能是否能拥有意识&#xff1f;这些问题一直困扰…

Android Binder技术概览

Android中的Binder是一种基于远程过程调用&#xff08;Remote Procedure Call, RPC&#xff09;的轻量级通信机制&#xff0c;核心用于 Android 系统中的进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;。Binder 是 Android 系统中不可或缺的一部分&#…

AI模型---安装cuda与cuDNN

1.安装cuda 先打开cmd 输入nvidia-smi 查看显卡支持cuda对应的版本&#xff1a; 然后去英伟达官网下载cuda&#xff08;外网多刷几次&#xff09; https://developer.nvidia.com/cuda-toolkit-archive 注意对应版本 安装过程中如果显示如下图&#xff1a; 请安装visual Stu…

ajax (一)

什么是 AJAX [ˈeɪdʒks] &#xff1f; 概念&#xff1a;AJAX是浏览器与服务器进行 数据通信 的技术&#xff0c;动态数据交互 怎么用AJAX? 1. 先使用 axios [k‘sio ʊ s] 库&#xff0c; 与服务器进行 数据通信 ⚫ 基于 XMLHttpRequest 封装、代码简单、月下载量在 1…

Load-Balanced-Online-OJ(负载均衡式在线OJ)

负载均衡式在线OJ 前言1. 项目介绍2. 所用技术与环境所用技术栈开发环境 3. 项目宏观结构3.1 项目核心模块3.2 项目的宏观结构 4. comm公共模块4.1 日志&#xff08;log.hpp &#xff09;4.1.1 日志主要内容4.1.2 日志使用方式4.1.2 日志代码 4.2 工具&#xff08;util.hpp&…

微信小程序上传微信官方审核流程(1)

1&#xff0c;打开微信开发者工具 2&#xff0c;微信开发者工具右上角有一个上传按钮&#xff0c;点击上传按钮 3&#xff0c;点击完上传按钮会弹出一个上传成功的提示&#xff0c;点击提示框中的确定按钮 4&#xff0c;点击完确定按钮后会显示填写版本好和项目备注 5&#x…

数据结构(Java版)第一期:时间复杂度和空间复杂度

目录 一、数据结构的概念 1.1. 什么是数据结构 1.2. 算法与数据结构的关系 二、算法效率 三、时间复杂度 3.1. 大O的渐进表⽰法 3.2. 计算冒泡排序的时间复杂度 3.3. 计算二分查找的时间复杂度 四、空间复杂度 4.1. 空间复杂度 4.2. 冒泡排序的空间复杂度 4.3.…

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程 引言 微信小程序作为一种新兴的轻量级应用,凭借其便捷性和丰富的功能受到了广泛的欢迎。在开发小程序的过程中,合理配置全局属性是提升用户体验的关键。本文将深入探讨小程序的全局配置中的window选项,重点介绍导…