C++获取系统开关机记录并打印(通过读取系统日志实现)


前言

IDE:VS2019
项目类型:C++控制台应用
系统:WIN7或WIN10
原理:通过调用Windows操作系统API查看windows事件日志中指定的事件ID来获取开关机时间,该记录所能获取的数据量取决于系统事件日志的缓存大小配置。其中,事件ID12表示正常开机,事件ID13表示正常关机,事件ID6008表示意外关闭。


一、直接查询事件日志过程

1.

在这里插入图片描述
点击开始按钮,在搜索框中输入“event”可以看到查看事件日志功能,也直接进入控制面板查找该功能。

2.

在这里插入图片描述
进入后点击左侧windows日志“系统”子菜单,即可查看所有系统日志信息。

3.

在这里插入图片描述
点击左侧筛选当前日志,在弹出窗体中箭头位置输入要筛选的ID。

4.

在这里插入图片描述
选中一条记录即可查看到该事件信息正常开机和关机可以直接使用事件记录的时间。

要注意的是,意外关闭事件是在正常开机之后生成的,因此事件写入的时间并不是实际意外关机的时间,意外关闭的时间在详细信息中,需要先解析出来。

二、代码查询开关机记录方案

使用代码查询开关机记录与上述可视化查询思路相同,需要用到三个windows系统API(Linux系统原理相同但API和事件ID不同)分别是:OpenEventLog、ReadEventLog、CloseEventLog。前两者是C++宏函数,根据项目编码类型自动解析为多字节版本和Unicode版本。非C/C++调用需要直接调用系统动态库中的相应版本而不能使用该宏。
调用OpenEventLog打开windows事件日志(第二个参数为“System”,否则读的不是系统事件),获得日志句柄。然后使用while循环调用ReadEventLog可以一直读到日志末尾而不局限于缓冲区的大小。在每次的调用结果中解析EVENTLOGRECORD类型的事件,将其中事件ID不是12,13,6008的筛选掉,12和13可以直接显示事件的记录时间,6008事件先解析事件描述,在显示事件描述中的时间,要注意的是该事件描述中的时间显示格式为"\09:37:38\0?2020/?12/?3\0",因此需要将日期提前并以“?”字符分割字符串在拼接时间来显示。

三、源码

#include <windows.h>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
	DWORD dwRead, dwNeeded;
	string Source;
	time_t Time;
	unsigned short ID;
	char Data[5120]; //缓冲区大小,如果太小读到的记录会少于windows日志
	HANDLE Log = OpenEventLog(NULL, _T"System");//第一次参数表示本地计算机,第二个参数表示系统日志
	if (Log == NULL)
	{
		return 0;
	}
	char pLogPath[40]{ "./SystemSwitchRecord.txt" };//存放路径
	ofstream write; //文件操作对象
	write.open(pLogPath, ios::out);//打开该文件,如果没有这个文件,会自动创建这个文件
	while (ReadEventLog(Log, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, Data, sizeof(Data), &dwRead, &dwNeeded))
	{
		for (DWORD i = 0; i < dwRead;)
		{
			EVENTLOGRECORD* ptr = (EVENTLOGRECORD*)(Data + i);
			Source = (TCHAR*)ptr + sizeof(EVENTLOGRECORD);//事件源
			ID = (unsigned short)ptr->EventID;//事件ID
			if (Source != "Microsoft-Windows-Kernel-General" && Source != "EventLog")
			{
				i += ptr->Length;
				continue;
			}
			string EventText = "";
			struct tm SysStartupTime;//时间结构体
			Time = ptr->TimeGenerated;//日期和时间
			char buf[64]{ 0 };//用来存放时间
			switch (ID)
			{
			case 12:
				EventText = "开机";
				localtime_s(&SysStartupTime, &Time);//时间戳转化为格式时间
				strftime(buf, _countof(buf), "%Y-%m-%d %H:%M:%S", &SysStartupTime);//格式化时间到缓冲区
				break;
			case 13:
				EventText = "关机";
				localtime_s(&SysStartupTime, &Time);//时间戳转化为格式时间
				strftime(buf, _countof(buf), "%Y-%m-%d %H:%M:%S", &SysStartupTime);//格式化时间到缓冲区
				break;
			case 6008:
			{
				EventText = "意外关闭";
				//获取事件详细信息
				char* SysShutDownTime = Data + i + ptr->StringOffset;//这里存储的是时分秒
				char* SysShutDownDate = NULL;
				char* SysShutDownDate_Next = NULL;
				//事件描述中存储的格式如下: char* data = "\09:37:38\0?2020/?12/?3\0",所以需要strtok_s()函数以"?"字符来分割字符串
				SysShutDownDate = strtok_s(SysShutDownTime + strlen(SysShutDownTime) + 2, "?", &SysShutDownDate_Next);
				while (SysShutDownDate != NULL)
				{
					memcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), SysShutDownDate, strlen(SysShutDownDate));
					SysShutDownDate = strtok_s(NULL, "?", &SysShutDownDate_Next);
				}
				strcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), " ");//增加一个空格
				strcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), SysShutDownTime);//复制时分秒
				break;
			}
			default:
				i += ptr->Length;
				continue;
			}
			write << "ID:" << ID << "  时间:" << buf << " " << EventText << endl;
			i += ptr->Length;
		}
	}
	CloseEventLog(Log);
	write.close(); // 输出完毕后关闭这个文件
	write.clear();
	system("pause");
	return 0;
}

四、附录

有的人
如果遇到这两行代码报错的问题,是因为项目的编码方式选择了unicode,右键项目->属性->高级 将编码方式切换为多字节字符集即可。
在这里插入图片描述


总结

以上就是使用C++读取系统开关机记录的内容了,如果您觉得这篇文章对您有帮助,还请不要吝啬您手里的赞。同样,如果您觉得这篇文章有误,请您指正批评,作者将感激不尽!

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jumul

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值