对于日志,大部分人的想法应当和我起初的想法一致,只要写个函数,在这个函数中打开一个文件,把需要记录的事件信息写到这个文件中。然后在需要记录日志的地方调用这个函数。
但此时需要考虑的问题有很多,包括文件名的定义,是每次写日志都创建一个新文件还是在一个文件中写所有的信息。如果是同一个文件如果控制多线程同时写的问题;还有当日志文件过大时,如果删除以前的记录。读日志的方式,包括检索的功能;安全问题等等。所以不要小看了仅是辅助功能的日志机制,但不用担心,windows已经为我们定义了很完善的日志架构。
Windows vista引入的CLFS ( Common Log File System)机制,另外一个就是从NT3.5就支持的Event Logging机制,里面的函数大多是以Elf(Event Log File)开头的,简称为ELF。
Windows XP中定义了3类日志,分别是应用程序日志(Application),安全日志(Security)和系统日志(system),文件名分别为AppEvent.Evt, SecEvent.Evt和SysEvent.Evt.这些文件都用于存储注册表文件和配置信息的CONFIG目录下%SystemRoot%\SYSTEM32\CONFIG\
Windows Vista增加了HardvareEvents和DFS Replication等日志类别,并且为所有的日志文件建立了一个单独的目录,即%SystemRoot%\SYSTEM32\winevt\Logs目录,日志文件的扩展名也由.EVT改为.EVTX。
日志的配置信息都是存储在注册表
HKEY_LOACAL_MACHINE\SYSTEM\CurrentControlSet\Sercices\Eventlog
下面看产生windows日志的API及其执行过程:
1. 在注册表中注册事件源。线介绍一下事件源,事件源都是注册在注册表HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\下的,如果需要自己注册事件源就可以在这个注册表下添加一个新的键,再加两个项就可以了。来看个例子,outlook的事件源
它包含了三个键,EvetMessageFile用来指定这个事件源的消息文件(message file)的位置和名称。消息文件的作用可以使用模板来格式化日志事件或其他消息,与字符串资源和对话框资源差不多。可以使用后面介绍的事件ID(Event ID)来查找对应的显示信息,提供一种模板的支持。TypesSupported表示事件所支持的类型,包括EVENTLOG_ERROR_TYPE(0x0001), EVENTLOG_WARBUBG_TYPE(0x0002)等。Version表示事件源的版本。消息文件可以是DLL,exe等有效地PE文件,编写消息文件的方法这里就不介绍了,这里可以写应用程序本身的路径。
2. 调用RegisterEventSource API来取得事件源句柄,原型为:
HANDLE RegisterEventSource ( LPCTSTR lpUNCServerName, LPCTSTR lpSourceName)
其中lpUNCServerName表示机器名,如果是在本机操作,添NULL就可以了,lpSourceName为事件源的名称,填写前面创建的键名就可以了。如果事件源的名称在注册表中找不到,那么系统会默认使用应用类日志下的Application事件源。
3. 使用ReportEvent API来添加日志记录。原型为:
BOOL ReportEvent ( HANLE hEventLog, WORD wType, WORD wCategory, DWORD dwEventID, PSID lpUserSid, DWORD wNumStrings, DWORD dwDataSize, LPCTSTR* lpStrings, LPVOID lpRawData);
第一参数为hEventLog是使用RegisterEventSource得到的事件源句柄;第二个参数wType用来指定事件的类型,可以为如下的常量:EVENTLOG_SUCCESS,EVENTLOG_AUDIT_FAILURE等。第三个参数wCategory用来指定事件在事件源中的类属号,其分类规则是由应用程序自己定义的。第四个参数dwEventID用来指定事件的ID号,通过这个ID来定位消息文件中所对应的信息。所以ID也是有应用程序自己定义的。第五个参数lpUserSid用来指定用户的安全标识,可以设为NULL。第六个参数wNumStrings用来指定第八个参数lpStrings所指向的字符串数组所包含的字符串指针个数。第八个参数用在事件的Description字段中显示信息,如下图所示:
第七个参数dwDataSize用来指定第九个参数lpRawData所指向的原始数据缓冲区的数据长度。这两个参数一个设为0,另一个设为NULL就行了。
这样就可以写入日子记录了,来看一下代码。
首先是注册事件源的代码:
BOOL AddEventSource(CString csName, DWORD dwCategoryCount)
{
HKEY hRegKey = NULL;
DWORD dwError = 0;
TCHAR szPath[ MAX_PATH ];
_stprintf( szPath, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s"), csName );
// Create the event source registry key
dwError = RegCreateKey( HKEY_LOCAL_MACHINE, szPath, &hRegKey );
if (dwError != 0)
{
OutMsg("RegCreateKey failed for %d",GetLastError());
return E_FAIL;
}
// Name of the PE module that contains the message resource
GetModuleFileName( NULL, szPath, MAX_PATH );
// Register EventMessageFile
dwError = RegSetValueEx( hRegKey, _T("EventMessageFile"), 0, REG_EXPAND_SZ, (PBYTE) szPath, (_tcslen( szPath) + 1) * sizeof TCHAR );
if (dwError == 0)
{
// Register supported event types
DWORD dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
dwError = RegSetValueEx( hRegKey, _T("TypesSupported"), 0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes );
// If we want to support event categories, we have also to register the CategoryMessageFile.
// and set CategoryCount. Note that categories need to have the message ids 1 to CategoryCount!
if(dwError == 0 && dwCategoryCount > 0 )
{
dwError = RegSetValueEx( hRegKey, _T("CategoryMessageFile"), 0, REG_EXPAND_SZ, (PBYTE) szPath, (_tcslen( szPath) + 1) * sizeof TCHAR );
if (dwError == 0)
dwError = RegSetValueEx( hRegKey, _T("CategoryCount"), 0, REG_DWORD, (PBYTE) &dwCategoryCount, sizeof dwCategoryCount );
}
}
RegCloseKey( hRegKey );
return TRUE;
}
来至《软件调试》
接下来是添加记录的过程:
LPCTSTR ppszArgs[]={"I make a trick!!!!!!!"}; // 要显示的信息
HANDLE hLog = ::RegisterEventSource( NULL, "MyEventSource"); //之前注册的事件源
BOOL bRet = ReportEvent(hLog, EVENTLOG_INFORMATION_TYPE,
0, 1024, NULL, 1, 0, ppszArgs, NULL); // 显示记录
除了显示日子记录的功能,当然还有其他的API来实现更多的功能,其他的API如下图所示: